mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
Mark the last non-empty FILE_DATA payload with BEGIN and END when the producer knows it is terminal. Complete producer bookkeeping in the following fileDataEnd call without consuming another LGMP grant. Keep the standalone terminal record for empty and legacy unknown streams. Drain up to 64 queued clipboard records per worker pass instead of sleeping after every message. Poll at 1 ms while transfers, blocked writes, held input, or pending output are active, and retain the 10 ms interval while idle. This removes a serialized grant round trip from every non-empty file response and reduces scheduling delay while a large transfer is active. Keep the 1 MiB payload size because the bottleneck was stop-and-wait latency, not the chunk capacity. Exercise a full 1 MiB response in a single BEGIN|END grant and retain coverage for empty unknown-size responses.
916 lines
31 KiB
C
916 lines
31 KiB
C
/**
|
|
* 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.h"
|
|
#include "core/clipboard_files.h"
|
|
#include "test.h"
|
|
|
|
#include "common/debug.h"
|
|
#include "common/KVMFRClipboard.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
static unsigned notices;
|
|
static uint64_t datasets[16];
|
|
static unsigned acquireCount;
|
|
static uint64_t acquiredDataset;
|
|
static uint64_t acquiredRequest;
|
|
static unsigned releaseCount;
|
|
static uint64_t releasedDataset;
|
|
static uint64_t releasedAcquisition;
|
|
static unsigned requestCount;
|
|
static LG_ClipboardFileRequest remoteRequest;
|
|
static unsigned remoteReadyCount;
|
|
static unsigned remoteFailedCount;
|
|
static uint64_t remoteFailedDataset;
|
|
static bool remoteRequestSucceeds = true;
|
|
static uint8_t * responseData;
|
|
static size_t responseSize;
|
|
static size_t responseCapacity;
|
|
static bool responseOpen;
|
|
static bool responseComplete;
|
|
static bool responseWireEnded;
|
|
static unsigned cancelCount;
|
|
static uint64_t cancelledDataset;
|
|
static uint64_t cancelledRequest;
|
|
static LG_ClipboardFileError cancelledReason;
|
|
|
|
void lgClipboard_notifyFiles(uint64_t dataset)
|
|
{
|
|
CHECK(notices < sizeof(datasets) / sizeof(datasets[0]));
|
|
datasets[notices++] = dataset;
|
|
}
|
|
|
|
bool lgClipboard_fileAcquire(uint64_t dataset, uint64_t acquisition)
|
|
{
|
|
++acquireCount;
|
|
acquiredDataset = dataset;
|
|
acquiredRequest = acquisition;
|
|
return true;
|
|
}
|
|
|
|
bool lgClipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
|
LG_ClipboardFileError error)
|
|
{
|
|
(void)dataset;
|
|
(void)acquisition;
|
|
(void)error;
|
|
return false;
|
|
}
|
|
|
|
bool lgClipboard_fileRelease(uint64_t dataset, uint64_t acquisition)
|
|
{
|
|
++releaseCount;
|
|
releasedDataset = dataset;
|
|
releasedAcquisition = acquisition;
|
|
return true;
|
|
}
|
|
|
|
bool lgClipboard_fileRequest(const LG_ClipboardFileRequest * request)
|
|
{
|
|
CHECK(request);
|
|
++requestCount;
|
|
remoteRequest = *request;
|
|
return remoteRequestSucceeds;
|
|
}
|
|
|
|
LG_ClipboardResult lgClipboard_fileDataBegin(
|
|
const LG_ClipboardFileRequest * request, uint64_t sizeHint)
|
|
{
|
|
CHECK(request);
|
|
(void)sizeHint;
|
|
responseSize = 0;
|
|
responseOpen = true;
|
|
responseComplete = false;
|
|
responseWireEnded = false;
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
LG_ClipboardResult lgClipboard_fileDataChunk(
|
|
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
|
const void * data, size_t size, bool end)
|
|
{
|
|
CHECK(request);
|
|
CHECK(responseOpen);
|
|
CHECK(responseOffset == responseSize);
|
|
CHECK(data || !size);
|
|
CHECK(size <= SIZE_MAX - responseSize);
|
|
const size_t wanted = responseSize + size;
|
|
if (wanted > responseCapacity)
|
|
{
|
|
uint8_t * resized = realloc(responseData, wanted);
|
|
CHECK(resized);
|
|
responseData = resized;
|
|
responseCapacity = wanted;
|
|
}
|
|
if (size)
|
|
memcpy(responseData + responseSize, data, size);
|
|
responseSize = wanted;
|
|
if (end)
|
|
responseWireEnded = true;
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
LG_ClipboardResult lgClipboard_fileDataEnd(
|
|
const LG_ClipboardFileRequest * request, uint64_t finalSize)
|
|
{
|
|
CHECK(request);
|
|
CHECK(responseOpen);
|
|
CHECK(finalSize == responseSize);
|
|
CHECK(!finalSize || responseWireEnded);
|
|
responseOpen = false;
|
|
responseComplete = true;
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
bool lgClipboard_fileCancel(uint64_t dataset, uint64_t request,
|
|
LG_ClipboardFileError reason)
|
|
{
|
|
++cancelCount;
|
|
cancelledDataset = dataset;
|
|
cancelledRequest = request;
|
|
cancelledReason = reason;
|
|
return true;
|
|
}
|
|
|
|
void lgClipboard_fileRemoteReady(uint64_t dataset)
|
|
{
|
|
CHECK(dataset == acquiredDataset);
|
|
++remoteReadyCount;
|
|
}
|
|
|
|
void lgClipboard_fileRemoteFailed(uint64_t dataset)
|
|
{
|
|
CHECK(dataset);
|
|
++remoteFailedCount;
|
|
remoteFailedDataset = dataset;
|
|
}
|
|
|
|
static bool runLocalRequest(uint64_t dataset, uint64_t node,
|
|
LG_ClipboardFileOperation operation, uint64_t offset, uint32_t length)
|
|
{
|
|
static uint64_t requestId = 1;
|
|
responseSize = 0;
|
|
responseOpen = false;
|
|
responseComplete = false;
|
|
responseWireEnded = false;
|
|
cancelCount = 0;
|
|
cancelledDataset = 0;
|
|
cancelledRequest = 0;
|
|
cancelledReason = LG_CLIPBOARD_FILE_ERROR_NONE;
|
|
const LG_ClipboardFileRequest request =
|
|
{
|
|
.dataset = dataset,
|
|
.request = requestId++,
|
|
.node = node,
|
|
.offset = offset,
|
|
.length = length,
|
|
.operation = operation,
|
|
};
|
|
const bool result = lgClipboardFiles_testLocalRequest(&request);
|
|
CHECK(!result || responseComplete);
|
|
CHECK(!result || !responseSize || responseWireEnded);
|
|
CHECK(result ? cancelCount == 0U : cancelCount == 1U);
|
|
if (!result)
|
|
{
|
|
CHECK(cancelledDataset == dataset);
|
|
CHECK(cancelledRequest == request.request);
|
|
CHECK(cancelledReason != LG_CLIPBOARD_FILE_ERROR_NONE);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool responseEntry(const char * name, KVMFRClipboardFileEntry * result)
|
|
{
|
|
size_t offset = 0;
|
|
while (offset < responseSize)
|
|
{
|
|
CHECK(responseSize - offset >= sizeof(*result));
|
|
KVMFRClipboardFileEntry entry;
|
|
memcpy(&entry, responseData + offset, sizeof(entry));
|
|
const uint64_t bytes = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(
|
|
entry.nameLength);
|
|
CHECK(bytes <= SIZE_MAX);
|
|
CHECK((size_t)bytes <= responseSize - offset);
|
|
const char * entryName = (const char *)responseData + offset +
|
|
sizeof(entry);
|
|
if (strlen(name) == entry.nameLength &&
|
|
!memcmp(entryName, name, entry.nameLength))
|
|
{
|
|
*result = entry;
|
|
return true;
|
|
}
|
|
offset += (size_t)bytes;
|
|
}
|
|
CHECK(offset == responseSize);
|
|
return false;
|
|
}
|
|
|
|
static void createFile(const char * path, const char * data)
|
|
{
|
|
const int fd = open(path,
|
|
O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
|
|
CHECK(fd >= 0);
|
|
const size_t length = strlen(data);
|
|
CHECK(write(fd, data, length) == (ssize_t)length);
|
|
CHECK(close(fd) == 0);
|
|
}
|
|
|
|
static void testLocalLifecycle(void)
|
|
{
|
|
char directory[] = "/tmp/lg-clipboard-files-XXXXXX";
|
|
CHECK(mkdtemp(directory));
|
|
char path[256];
|
|
CHECK(snprintf(path, sizeof(path), "%s/item.txt", directory) > 0);
|
|
const int fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
|
|
CHECK(fd >= 0);
|
|
CHECK(write(fd, "clipboard", 9) == 9);
|
|
CHECK(close(fd) == 0);
|
|
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x1234000012340000)));
|
|
char uri[320];
|
|
const int uriLength = snprintf(uri, sizeof(uri), "file://%s\r\n", path);
|
|
CHECK(uriLength > 0 && (size_t)uriLength < sizeof(uri));
|
|
CHECK(lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)uriLength));
|
|
CHECK(notices == 1);
|
|
CHECK(datasets[0] != 0);
|
|
CHECK(kvmfrClipboardTransferFromClient(datasets[0]));
|
|
|
|
lgClipboardFiles_clearLocal();
|
|
char gnome[336];
|
|
const int gnomeLength = snprintf(
|
|
gnome, sizeof(gnome), "copy\nfile://%s\n", path);
|
|
CHECK(gnomeLength > 0 && (size_t)gnomeLength < sizeof(gnome));
|
|
CHECK(lgClipboardFiles_setLocal("x-special/gnome-copied-files",
|
|
gnome, (size_t)gnomeLength));
|
|
CHECK(notices == 2);
|
|
CHECK(datasets[1] != datasets[0]);
|
|
|
|
static const char invalid[] = "file:///does/not/exist\n";
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", invalid, sizeof(invalid) - 1U));
|
|
lgClipboardFiles_clearLocal();
|
|
lgClipboardFiles_free();
|
|
lgClipboardFiles_free();
|
|
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x5678000056780000)));
|
|
CHECK(lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)uriLength));
|
|
CHECK(notices == 3);
|
|
CHECK(datasets[2] != datasets[0]);
|
|
CHECK(datasets[2] != datasets[1]);
|
|
lgClipboardFiles_free();
|
|
|
|
CHECK(unlink(path) == 0);
|
|
CHECK(rmdir(directory) == 0);
|
|
}
|
|
|
|
static void testLocalUriValidation(void)
|
|
{
|
|
char directory[] = "/tmp/lg-clipboard-uri-XXXXXX";
|
|
CHECK(mkdtemp(directory));
|
|
|
|
char plain[256];
|
|
char spaced[256];
|
|
char link[256];
|
|
char alias[256];
|
|
CHECK(snprintf(plain, sizeof(plain), "%s/item.txt", directory) > 0);
|
|
CHECK(snprintf(spaced, sizeof(spaced),
|
|
"%s/item space.txt", directory) > 0);
|
|
CHECK(snprintf(link, sizeof(link), "%s/link.txt", directory) > 0);
|
|
CHECK(snprintf(alias, sizeof(alias), "%s/alias", directory) > 0);
|
|
createFile(plain, "plain");
|
|
createFile(spaced, "spaced");
|
|
CHECK(symlink("item.txt", link) == 0);
|
|
CHECK(symlink(".", alias) == 0);
|
|
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x684b2452684b2452)));
|
|
const unsigned before = notices;
|
|
char uri[512];
|
|
int length = snprintf(uri, sizeof(uri),
|
|
"FILE://LOCALHOST%s/item%%20space.txt\r\n", directory);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
lgClipboardFiles_clearLocal();
|
|
|
|
length = snprintf(uri, sizeof(uri), "file://%s%%\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s%%0\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s%%GG\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s%%00suffix\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s?query\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s#fragment\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://example.invalid%s\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
static const char root[] = "file:///\n";
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", root, sizeof(root) - 1U));
|
|
length = snprintf(uri, sizeof(uri), "file://%s/\n", directory);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
lgClipboardFiles_clearLocal();
|
|
length = snprintf(uri, sizeof(uri), "file://%s/./item.txt\n", directory);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s//item.txt\n", directory);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s\n", link);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri),
|
|
"file://%s/alias/item.txt\n", directory);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri), "file://%s\n", spaced);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)length));
|
|
length = snprintf(uri, sizeof(uri),
|
|
"cutx\nfile://%s\n", plain);
|
|
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
|
CHECK(!lgClipboardFiles_setLocal(
|
|
"x-special/gnome-copied-files", uri, (size_t)length));
|
|
CHECK(notices == before + 2U);
|
|
|
|
lgClipboardFiles_free();
|
|
CHECK(unlink(alias) == 0);
|
|
CHECK(unlink(link) == 0);
|
|
CHECK(unlink(spaced) == 0);
|
|
CHECK(unlink(plain) == 0);
|
|
CHECK(rmdir(directory) == 0);
|
|
}
|
|
|
|
static void testLocalSnapshot(void)
|
|
{
|
|
char directory[] = "/tmp/lg-clipboard-snapshot-XXXXXX";
|
|
CHECK(mkdtemp(directory));
|
|
char stable[256];
|
|
char changed[256];
|
|
CHECK(snprintf(stable, sizeof(stable),
|
|
"%s/stable.txt", directory) > 0);
|
|
CHECK(snprintf(changed, sizeof(changed),
|
|
"%s/changed.txt", directory) > 0);
|
|
createFile(stable, "stable");
|
|
createFile(changed, "before");
|
|
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x1a72021b1a72021b)));
|
|
char uri[320];
|
|
const int uriLength = snprintf(
|
|
uri, sizeof(uri), "file://%s\r\n", directory);
|
|
CHECK(uriLength > 0 && (size_t)uriLength < sizeof(uri));
|
|
CHECK(lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)uriLength));
|
|
const uint64_t dataset = datasets[notices - 1U];
|
|
|
|
CHECK(runLocalRequest(dataset, KVMFR_CLIPBOARD_FILE_ROOT_NODE,
|
|
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
|
const char * base = strrchr(directory, '/');
|
|
CHECK(base && base[1]);
|
|
KVMFRClipboardFileEntry rootEntry;
|
|
CHECK(responseEntry(base + 1, &rootEntry));
|
|
CHECK(rootEntry.type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY);
|
|
|
|
CHECK(runLocalRequest(dataset, rootEntry.node,
|
|
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
|
KVMFRClipboardFileEntry stableEntry;
|
|
KVMFRClipboardFileEntry changedEntry;
|
|
CHECK(responseEntry("stable.txt", &stableEntry));
|
|
CHECK(responseEntry("changed.txt", &changedEntry));
|
|
|
|
char moved[320];
|
|
CHECK(snprintf(moved, sizeof(moved), "%s-moved", directory) > 0);
|
|
CHECK(rename(directory, moved) == 0);
|
|
CHECK(mkdir(directory, 0700) == 0);
|
|
char replacement[384];
|
|
CHECK(snprintf(replacement, sizeof(replacement),
|
|
"%s/stable.txt", directory) > 0);
|
|
createFile(replacement, "replacement");
|
|
|
|
CHECK(runLocalRequest(dataset, stableEntry.node,
|
|
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)stableEntry.size));
|
|
CHECK(responseSize == strlen("stable"));
|
|
CHECK(!memcmp(responseData, "stable", responseSize));
|
|
lgClipboardFiles_testForceLocalEof();
|
|
CHECK(!runLocalRequest(dataset, stableEntry.node,
|
|
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)stableEntry.size));
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_IO);
|
|
|
|
char movedChanged[384];
|
|
CHECK(snprintf(movedChanged, sizeof(movedChanged),
|
|
"%s/changed.txt", moved) > 0);
|
|
const int changedFd = open(movedChanged,
|
|
O_WRONLY | O_APPEND | O_CLOEXEC);
|
|
CHECK(changedFd >= 0);
|
|
CHECK(write(changedFd, "-modified", 9) == 9);
|
|
CHECK(close(changedFd) == 0);
|
|
CHECK(!runLocalRequest(dataset, changedEntry.node,
|
|
LG_CLIPBOARD_FILE_READ, 0, 64));
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_INVALID);
|
|
CHECK(!runLocalRequest(dataset, changedEntry.node,
|
|
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)changedEntry.size));
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_STALE);
|
|
|
|
char movedStable[384];
|
|
char oldStable[384];
|
|
CHECK(snprintf(movedStable, sizeof(movedStable),
|
|
"%s/stable.txt", moved) > 0);
|
|
CHECK(snprintf(oldStable, sizeof(oldStable),
|
|
"%s/stable.old", moved) > 0);
|
|
CHECK(rename(movedStable, oldStable) == 0);
|
|
createFile(movedStable, "stable");
|
|
CHECK(!runLocalRequest(dataset, stableEntry.node,
|
|
LG_CLIPBOARD_FILE_READ, 0, 64));
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_INVALID);
|
|
CHECK(!runLocalRequest(dataset, stableEntry.node,
|
|
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)stableEntry.size));
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_STALE);
|
|
|
|
lgClipboardFiles_free();
|
|
CHECK(unlink(replacement) == 0);
|
|
CHECK(rmdir(directory) == 0);
|
|
CHECK(unlink(movedStable) == 0);
|
|
CHECK(unlink(oldStable) == 0);
|
|
CHECK(unlink(movedChanged) == 0);
|
|
CHECK(rmdir(moved) == 0);
|
|
}
|
|
|
|
static void testLocalUnsupportedEntry(void)
|
|
{
|
|
char directory[] = "/tmp/lg-clipboard-unsupported-XXXXXX";
|
|
CHECK(mkdtemp(directory));
|
|
char target[256];
|
|
char link[256];
|
|
char fifo[256];
|
|
CHECK(snprintf(target, sizeof(target),
|
|
"%s/target.txt", directory) > 0);
|
|
CHECK(snprintf(link, sizeof(link), "%s/link.txt", directory) > 0);
|
|
CHECK(snprintf(fifo, sizeof(fifo), "%s/special", directory) > 0);
|
|
createFile(target, "target");
|
|
CHECK(symlink("target.txt", link) == 0);
|
|
CHECK(mkfifo(fifo, 0600) == 0);
|
|
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x62d1147362d11473)));
|
|
char uri[320];
|
|
const int uriLength = snprintf(
|
|
uri, sizeof(uri), "file://%s\r\n", directory);
|
|
CHECK(uriLength > 0 && (size_t)uriLength < sizeof(uri));
|
|
CHECK(lgClipboardFiles_setLocal(
|
|
"text/uri-list", uri, (size_t)uriLength));
|
|
const uint64_t dataset = datasets[notices - 1U];
|
|
CHECK(runLocalRequest(dataset, KVMFR_CLIPBOARD_FILE_ROOT_NODE,
|
|
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
|
const char * base = strrchr(directory, '/');
|
|
CHECK(base && base[1]);
|
|
KVMFRClipboardFileEntry rootEntry;
|
|
CHECK(responseEntry(base + 1, &rootEntry));
|
|
CHECK(!runLocalRequest(dataset, rootEntry.node,
|
|
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
|
|
|
|
lgClipboardFiles_free();
|
|
CHECK(unlink(fifo) == 0);
|
|
CHECK(unlink(link) == 0);
|
|
CHECK(unlink(target) == 0);
|
|
CHECK(rmdir(directory) == 0);
|
|
}
|
|
|
|
static uint64_t completeRemoteOffer(uint64_t dataset, const char * name)
|
|
{
|
|
const unsigned previousAcquires = acquireCount;
|
|
const unsigned previousRequests = requestCount;
|
|
const unsigned previousReady = remoteReadyCount;
|
|
CHECK(lgClipboardFiles_remoteOffer(dataset));
|
|
CHECK(acquireCount == previousAcquires + 1U);
|
|
CHECK(acquiredDataset == dataset);
|
|
const uint64_t acquisition = acquiredRequest;
|
|
|
|
lgClipboardFiles_remoteAcquired(dataset, acquisition,
|
|
LG_CLIPBOARD_FILE_ERROR_NONE);
|
|
CHECK(requestCount == previousRequests + 1U);
|
|
CHECK(remoteRequest.dataset == dataset);
|
|
CHECK(remoteRequest.node == KVMFR_CLIPBOARD_FILE_ROOT_NODE);
|
|
CHECK(remoteRequest.operation == LG_CLIPBOARD_FILE_LIST);
|
|
|
|
const size_t nameLength = strlen(name);
|
|
const uint64_t wireSize = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(nameLength);
|
|
CHECK(wireSize <= SIZE_MAX);
|
|
uint8_t * wire = calloc(1, (size_t)wireSize);
|
|
CHECK(wire);
|
|
const KVMFRClipboardFileEntry entry =
|
|
{
|
|
.node = 2,
|
|
.size = 9,
|
|
.createdNs = 1,
|
|
.modifiedNs = 2,
|
|
.type = KVMFR_CLIPBOARD_FILE_TYPE_REGULAR,
|
|
.nameLength = (uint16_t)nameLength,
|
|
};
|
|
memcpy(wire, &entry, sizeof(entry));
|
|
memcpy(wire + sizeof(entry), name, nameLength);
|
|
CHECK(lgClipboardFiles_remoteDataBegin(
|
|
&remoteRequest, wireSize) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(lgClipboardFiles_remoteDataChunk(&remoteRequest, 0,
|
|
wire, (size_t)wireSize) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(lgClipboardFiles_remoteDataEnd(
|
|
&remoteRequest, wireSize) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
free(wire);
|
|
CHECK(remoteReadyCount == previousReady + 1U);
|
|
CHECK(lgClipboardFiles_remoteReady(dataset));
|
|
return acquisition;
|
|
}
|
|
|
|
static uint64_t beginRemoteOffer(uint64_t dataset)
|
|
{
|
|
const unsigned previousAcquires = acquireCount;
|
|
CHECK(lgClipboardFiles_remoteOffer(dataset));
|
|
CHECK(acquireCount == previousAcquires + 1U);
|
|
CHECK(acquiredDataset == dataset);
|
|
return acquiredRequest;
|
|
}
|
|
|
|
static LG_ClipboardFileRequest beginRemoteList(uint64_t dataset,
|
|
uint64_t * acquisition)
|
|
{
|
|
const unsigned previousRequests = requestCount;
|
|
*acquisition = beginRemoteOffer(dataset);
|
|
lgClipboardFiles_remoteAcquired(dataset, *acquisition,
|
|
LG_CLIPBOARD_FILE_ERROR_NONE);
|
|
CHECK(requestCount == previousRequests + 1U);
|
|
CHECK(remoteRequest.dataset == dataset);
|
|
CHECK(remoteRequest.operation == LG_CLIPBOARD_FILE_LIST);
|
|
return remoteRequest;
|
|
}
|
|
|
|
static void checkRemoteFailure(uint64_t dataset,
|
|
unsigned previousFailures, unsigned previousReleases,
|
|
bool acquired)
|
|
{
|
|
CHECK(remoteFailedCount == previousFailures + 1U);
|
|
CHECK(remoteFailedDataset == dataset);
|
|
CHECK(releaseCount == previousReleases + (acquired ? 1U : 0U));
|
|
if (acquired)
|
|
CHECK(releasedDataset == dataset);
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
|
}
|
|
|
|
static void testRemoteFailures(void)
|
|
{
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x7315000073150000)));
|
|
acquireCount = 0;
|
|
releaseCount = 0;
|
|
requestCount = 0;
|
|
remoteFailedCount = 0;
|
|
remoteFailedDataset = 0;
|
|
remoteRequestSucceeds = true;
|
|
|
|
uint64_t dataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
UINT64_C(0x73150001);
|
|
uint64_t acquisition = beginRemoteOffer(dataset);
|
|
unsigned previousFailures = remoteFailedCount;
|
|
unsigned previousReleases = releaseCount;
|
|
lgClipboardFiles_remoteAcquired(dataset, acquisition,
|
|
LG_CLIPBOARD_FILE_ERROR_ACCESS);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, false);
|
|
|
|
dataset += 1U;
|
|
acquisition = beginRemoteOffer(dataset);
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
lgClipboardFiles_remoteCancel(dataset, acquisition,
|
|
LG_CLIPBOARD_FILE_ERROR_CANCELLED);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, false);
|
|
|
|
dataset += 1U;
|
|
LG_ClipboardFileRequest request = beginRemoteList(dataset, &acquisition);
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
lgClipboardFiles_remoteCancel(dataset, request.request,
|
|
LG_CLIPBOARD_FILE_ERROR_CANCELLED);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
|
|
|
dataset += 1U;
|
|
acquisition = beginRemoteOffer(dataset);
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
remoteRequestSucceeds = false;
|
|
lgClipboardFiles_remoteAcquired(dataset, acquisition,
|
|
LG_CLIPBOARD_FILE_ERROR_NONE);
|
|
remoteRequestSucceeds = true;
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
|
|
|
dataset += 1U;
|
|
request = beginRemoteList(dataset, &acquisition);
|
|
LG_ClipboardFileRequest malformed = request;
|
|
malformed.node += 1U;
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
CHECK(lgClipboardFiles_remoteDataBegin(&malformed, 0) ==
|
|
LG_CLIPBOARD_RESULT_FAILED);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
|
|
|
dataset += 1U;
|
|
request = beginRemoteList(dataset, &acquisition);
|
|
CHECK(lgClipboardFiles_remoteDataBegin(&request, 1) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
CHECK(lgClipboardFiles_remoteDataChunk(&request, 0, NULL, 1) ==
|
|
LG_CLIPBOARD_RESULT_FAILED);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
|
|
|
dataset += 1U;
|
|
request = beginRemoteList(dataset, &acquisition);
|
|
CHECK(lgClipboardFiles_remoteDataBegin(&request, 1) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
CHECK(lgClipboardFiles_remoteDataEnd(&request, 0) ==
|
|
LG_CLIPBOARD_RESULT_FAILED);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
|
|
|
dataset += 1U;
|
|
request = beginRemoteList(dataset, &acquisition);
|
|
static const char invalidName[] = "bad/name";
|
|
const size_t wireSize = (size_t)KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(
|
|
sizeof(invalidName) - 1U);
|
|
uint8_t * wire = calloc(1, wireSize);
|
|
CHECK(wire);
|
|
const KVMFRClipboardFileEntry entry =
|
|
{
|
|
.node = 2,
|
|
.size = 1,
|
|
.type = KVMFR_CLIPBOARD_FILE_TYPE_REGULAR,
|
|
.nameLength = sizeof(invalidName) - 1U,
|
|
};
|
|
memcpy(wire, &entry, sizeof(entry));
|
|
memcpy(wire + sizeof(entry), invalidName, sizeof(invalidName) - 1U);
|
|
CHECK(lgClipboardFiles_remoteDataBegin(&request, wireSize) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(lgClipboardFiles_remoteDataChunk(&request, 0, wire, wireSize) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
previousFailures = remoteFailedCount;
|
|
previousReleases = releaseCount;
|
|
CHECK(lgClipboardFiles_remoteDataEnd(&request, wireSize) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
free(wire);
|
|
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
|
|
|
lgClipboardFiles_free();
|
|
}
|
|
|
|
static void checkRemoteUri(uint64_t presentation, const char * name)
|
|
{
|
|
char * uri = NULL;
|
|
size_t size = 0;
|
|
CHECK(lgClipboardFiles_getRemotePresentation(
|
|
presentation, "text/uri-list", &uri, &size));
|
|
CHECK(uri);
|
|
CHECK(size == strlen(uri));
|
|
CHECK(strstr(uri, name));
|
|
free(uri);
|
|
}
|
|
|
|
typedef struct ReleasePresentationTask
|
|
{
|
|
pthread_barrier_t * barrier;
|
|
uint64_t presentation;
|
|
}
|
|
ReleasePresentationTask;
|
|
|
|
static void * releasePresentation(void * opaque)
|
|
{
|
|
ReleasePresentationTask * task = opaque;
|
|
const int result = pthread_barrier_wait(task->barrier);
|
|
CHECK(result == 0 || result == PTHREAD_BARRIER_SERIAL_THREAD);
|
|
lgClipboardFiles_remotePresentationRelease(task->presentation);
|
|
return NULL;
|
|
}
|
|
|
|
static void releasePresentationConcurrently(uint64_t presentation)
|
|
{
|
|
pthread_barrier_t barrier;
|
|
CHECK(pthread_barrier_init(&barrier, NULL, 3) == 0);
|
|
ReleasePresentationTask task =
|
|
{
|
|
.barrier = &barrier,
|
|
.presentation = presentation,
|
|
};
|
|
pthread_t first;
|
|
pthread_t second;
|
|
CHECK(pthread_create(&first, NULL, releasePresentation, &task) == 0);
|
|
CHECK(pthread_create(&second, NULL, releasePresentation, &task) == 0);
|
|
const int result = pthread_barrier_wait(&barrier);
|
|
CHECK(result == 0 || result == PTHREAD_BARRIER_SERIAL_THREAD);
|
|
CHECK(pthread_join(first, NULL) == 0);
|
|
CHECK(pthread_join(second, NULL) == 0);
|
|
CHECK(pthread_barrier_destroy(&barrier) == 0);
|
|
}
|
|
|
|
static void testRemoteLifecycle(void)
|
|
{
|
|
CHECK(lgClipboardFiles_testFuseStopWake());
|
|
CHECK(lgClipboardFiles_testInit(UINT64_C(0x7311000073110000)));
|
|
CHECK(lgClipboardFiles_testUnsentRemoteOwnership());
|
|
acquireCount = 0;
|
|
releaseCount = 0;
|
|
requestCount = 0;
|
|
remoteReadyCount = 0;
|
|
cancelCount = 0;
|
|
|
|
const uint64_t firstDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
UINT64_C(0x73110001);
|
|
const uint64_t first = completeRemoteOffer(firstDataset, "first.txt");
|
|
|
|
CHECK(lgClipboardFiles_testRemoteRead(first, 2, 0, 9));
|
|
LG_ClipboardFileRequest shortRead = remoteRequest;
|
|
CHECK(lgClipboardFiles_remoteDataBegin(&shortRead, 8) ==
|
|
LG_CLIPBOARD_RESULT_FAILED);
|
|
|
|
CHECK(lgClipboardFiles_testRemoteRead(first, 2, 0, 9));
|
|
shortRead = remoteRequest;
|
|
CHECK(lgClipboardFiles_remoteDataBegin(&shortRead, 9) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
const uint8_t shortData[8] = { 0 };
|
|
CHECK(lgClipboardFiles_remoteDataChunk(&shortRead, 0,
|
|
shortData, sizeof(shortData)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(lgClipboardFiles_remoteDataEnd(&shortRead, sizeof(shortData)) ==
|
|
LG_CLIPBOARD_RESULT_FAILED);
|
|
|
|
const uint64_t firstPresentation =
|
|
lgClipboardFiles_remotePresentationAcquire();
|
|
CHECK(firstPresentation == first);
|
|
checkRemoteUri(firstPresentation, "first.txt");
|
|
|
|
const uint64_t secondDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
UINT64_C(0x73110002);
|
|
completeRemoteOffer(secondDataset, "second.txt");
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 2U);
|
|
CHECK(releaseCount == 0U);
|
|
checkRemoteUri(firstPresentation, "first.txt");
|
|
lgClipboardFiles_remotePresentationRelease(firstPresentation);
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
|
CHECK(releaseCount == 1U);
|
|
CHECK(releasedDataset == firstDataset);
|
|
CHECK(releasedAcquisition == first);
|
|
|
|
const uint64_t secondPresentation =
|
|
lgClipboardFiles_remotePresentationAcquire();
|
|
CHECK(secondPresentation != 0);
|
|
checkRemoteUri(secondPresentation, "second.txt");
|
|
lgClipboardFiles_remotePresentationDelivered(secondPresentation);
|
|
lgClipboardFiles_remotePresentationRelease(secondPresentation);
|
|
const uint64_t thirdDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
UINT64_C(0x73110003);
|
|
completeRemoteOffer(thirdDataset, "third.txt");
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 2U);
|
|
CHECK(releaseCount == 1U);
|
|
CHECK(lgClipboardFiles_testBeginRemoteLookup(secondPresentation));
|
|
lgClipboardFiles_testExpireRemoteDeliveries();
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 2U);
|
|
CHECK(releaseCount == 1U);
|
|
lgClipboardFiles_testEndRemoteLookup(secondPresentation);
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
|
CHECK(releaseCount == 2U);
|
|
CHECK(releasedDataset == secondDataset);
|
|
lgClipboardFiles_remoteClear();
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
|
CHECK(releaseCount == 3U);
|
|
|
|
for (uint64_t i = 0; i < 10; ++i)
|
|
{
|
|
const uint64_t dataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
(UINT64_C(0x73120000) + i);
|
|
const uint64_t acquisition =
|
|
completeRemoteOffer(dataset, "grace.txt");
|
|
const uint64_t presentation =
|
|
lgClipboardFiles_remotePresentationAcquire();
|
|
CHECK(presentation == acquisition);
|
|
checkRemoteUri(presentation, "grace.txt");
|
|
lgClipboardFiles_remotePresentationDelivered(presentation);
|
|
lgClipboardFiles_remotePresentationRelease(presentation);
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() <= 5U);
|
|
}
|
|
lgClipboardFiles_remoteClear();
|
|
lgClipboardFiles_testExpireRemoteDeliveries();
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
|
|
|
for (uint64_t i = 0; i < 16; ++i)
|
|
{
|
|
const uint64_t oldDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
(UINT64_C(0x73130000) + i * 2U);
|
|
const uint64_t oldAcquisition =
|
|
completeRemoteOffer(oldDataset, "old.txt");
|
|
const uint64_t presentation =
|
|
lgClipboardFiles_remotePresentationAcquire();
|
|
CHECK(presentation == oldAcquisition);
|
|
const uint64_t currentDataset = oldDataset + 1U;
|
|
completeRemoteOffer(currentDataset, "current.txt");
|
|
const unsigned previousReleases = releaseCount;
|
|
releasePresentationConcurrently(presentation);
|
|
CHECK(releaseCount == previousReleases + 1U);
|
|
CHECK(releasedDataset == oldDataset);
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
|
lgClipboardFiles_remoteClear();
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
|
}
|
|
|
|
const uint64_t pendingDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
|
UINT64_C(0x73140001);
|
|
CHECK(lgClipboardFiles_remoteOffer(pendingDataset));
|
|
CHECK(acquiredDataset == pendingDataset);
|
|
const uint64_t pendingAcquisition = acquiredRequest;
|
|
const uint64_t replacementDataset = pendingDataset + 1U;
|
|
const unsigned previousCancels = cancelCount;
|
|
CHECK(lgClipboardFiles_remoteOffer(replacementDataset));
|
|
CHECK(cancelCount == previousCancels + 1U);
|
|
CHECK(cancelledDataset == pendingDataset);
|
|
CHECK(cancelledRequest == pendingAcquisition);
|
|
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_CANCELLED);
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
|
lgClipboardFiles_remoteClear();
|
|
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
|
lgClipboardFiles_free();
|
|
}
|
|
|
|
static int testMount(void)
|
|
{
|
|
if (access("/dev/fuse", R_OK | W_OK) < 0)
|
|
return 77;
|
|
if (!getenv("XDG_RUNTIME_DIR"))
|
|
CHECK(setenv("XDG_RUNTIME_DIR", "/tmp", 1) == 0);
|
|
CHECK(lgClipboardFiles_init());
|
|
lgClipboardFiles_free();
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
debug_init();
|
|
|
|
if (argc == 2 && !strcmp(argv[1], "mount"))
|
|
return testMount();
|
|
if (argc == 2 && !strcmp(argv[1], "failures"))
|
|
{
|
|
testRemoteFailures();
|
|
return 0;
|
|
}
|
|
testLocalLifecycle();
|
|
testLocalUriValidation();
|
|
testLocalSnapshot();
|
|
testLocalUnsupportedEntry();
|
|
testRemoteLifecycle();
|
|
free(responseData);
|
|
return 0;
|
|
}
|