/**
 * 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
 */

#define FUSE_USE_VERSION 310

#include "core/clipboard_files.h"
#include "core/clipboard.h"

#include "common/KVMFRClipboard.h"
#include "common/debug.h"
#include "common/event.h"
#include "common/locking.h"
#include "common/thread.h"
#include "common/time.h"

#include <fuse3/fuse_lowlevel.h>

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <poll.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/eventfd.h>
#include <sys/random.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>

#define FILE_WORKERS             4U
#define FILE_ATTR_TIMEOUT        1.0
#define FILE_ENTRY_TIMEOUT       1.0
#define FILE_REQUEST_MAX         KVMFR_CLIPBOARD_FILE_READ_BYTES
#define FILE_STREAM_CHUNK        KVMFR_CLIPBOARD_DATA_BYTES
#define FILE_DELIVERY_GRACE_US   UINT64_C(30000000)
#define FILE_DELIVERY_GRACE_MAX  4U
#define FILE_URI_PREFIX          "file://"
#define FILE_GNOME_MIME          "x-special/gnome-copied-files"
#define FILE_MATE_MIME           "x-special/mate-copied-files"
#define FILE_URI_MIME            "text/uri-list"
#define FILE_KDE_CUT_MIME        "application/x-kde-cutselection"

_Static_assert(FILE_REQUEST_MAX <= UINT32_MAX,
    "clipboard FUSE read size exceeds the FUSE protocol field");

enum RemoteReply
{
  REMOTE_REPLY_INITIAL,
  REMOTE_REPLY_LOOKUP,
  REMOTE_REPLY_READDIR,
  REMOTE_REPLY_READ,
};

struct RemoteDataset;

struct RemoteNode
{
  struct RemoteNode    * next;
  struct RemoteDataset * dataset;
  fuse_ino_t             ino;
  fuse_ino_t             parent;
  uint64_t               node;
  uint64_t               size;
  uint64_t               createdNs;
  uint64_t               modifiedNs;
  KVMFRClipboardFileType type;
  uint64_t               lookups;
  bool                   listed;
  char                 * name;
};

struct RemoteDataset
{
  struct RemoteDataset * next;
  uint64_t               dataset;
  uint64_t               acquisition;
  fuse_ino_t             ino;
  uint64_t               lookupPins;
  uint64_t               openPins;
  uint64_t               requestPins;
  uint64_t               operationPins;
  uint64_t               deliveryDeadline;
  unsigned               presentationPins;
  bool                   acquiring;
  bool                   acquired;
  bool                   ready;
  bool                   current;
  bool                   stale;
  bool                   deliveryPending;
  bool                   pruneQueued;
  char                   name[40];
};

struct RemoteRequest
{
  struct RemoteRequest       * next;
  LG_ClipboardFileRequest      request;
  struct RemoteDataset       * dataset;
  struct RemoteNode          * directory;
  enum RemoteReply             reply;
  fuse_req_t                   fuseRequest;
  size_t                       fuseSize;
  off_t                        fuseOffset;
  char                       * lookupName;
  uint8_t                    * data;
  size_t                       dataSize;
  size_t                       dataCapacity;
  uint64_t                     sizeHint;
  bool                         began;
  bool                         transportStarted;
};

enum RemoteActionType
{
  REMOTE_ACTION_NONE,
  REMOTE_ACTION_CANCEL_ACQUISITION,
  REMOTE_ACTION_RELEASE_ACQUISITION,
};

struct RemoteAction
{
  enum RemoteActionType   type;
  struct RemoteDataset  * object;
  uint64_t                dataset;
  uint64_t                acquisition;
};

struct LocalDataset;

struct LocalNode
{
  struct LocalNode    * next;
  struct LocalDataset * dataset;
  struct LocalNode    * root;
  uint64_t              node;
  uint64_t              parent;
  uint64_t              size;
  uint64_t              createdNs;
  uint64_t              modifiedNs;
  KVMFRClipboardFileType type;
  dev_t                 device;
  ino_t                 inode;
  int                   fd;
  char                 * path;
  char                 * name;
};

struct LocalDataset
{
  struct LocalDataset * next;
  struct LocalNode    * nodes;
  uint64_t              nodeSerial;
  uint64_t              dataset;
  unsigned              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;
};

static struct
{
  LG_Lock                    lock;
  struct fuse_session      * session;
  LGThread                 * fuseThread;
  int                        fuseStopFd;
  char                       mountpoint[PATH_MAX];
  bool                       initialized;
  bool                       stopping;
  fuse_ino_t                 inodeSerial;
  uint64_t                   requestSerial;
  uint64_t                   datasetSerial;
  struct RemoteDataset     * remoteDatasets;
  struct RemoteDataset     * remoteCurrent;
  struct RemoteNode        * remoteNodes;
  struct RemoteRequest     * remoteRequests;
  struct LocalDataset      * localDatasets;
  struct LocalDataset      * localCurrent;
  struct LocalAcquisition * localAcquisitions;
  struct LocalJob          * jobsHead;
  struct LocalJob         ** jobsTail;
  struct LocalJob          * activeJobs;
  LGEvent                  * jobEvent;
  LGThread                 * workers[FILE_WORKERS];
}
files;

static bool filesLockInitialized;

#ifdef ENABLE_TESTS
static atomic_bool forceLocalEof;
#endif

static bool randomBytes(void * buffer, size_t size)
{
  uint8_t * bytes = buffer;
  size_t offset = 0;
  while (offset < size)
  {
    const ssize_t result = getrandom(bytes + offset, size - offset, 0);
    if (result < 0 && errno == EINTR)
      continue;
    if (result <= 0)
    {
      if (!result)
        errno = EIO;
      return false;
    }
    offset += (size_t)result;
  }
  return true;
}

static uint64_t nsFromTimespec(struct timespec value)
{
  if (value.tv_sec < 0 || value.tv_nsec < 0)
    return 0;
  if ((uint64_t)value.tv_sec > UINT64_MAX / UINT64_C(1000000000))
    return UINT64_MAX;
  return (uint64_t)value.tv_sec * UINT64_C(1000000000) +
    (uint64_t)value.tv_nsec;
}

static struct timespec timespecFromNs(uint64_t value)
{
  return (struct timespec)
  {
    .tv_sec  = (time_t)(value / UINT64_C(1000000000)),
    .tv_nsec = (long)(value % UINT64_C(1000000000)),
  };
}

static uint64_t nextRequestNL(void)
{
  do
    ++files.requestSerial;
  while (!files.requestSerial ||
      kvmfrClipboardTransferFromHelper(files.requestSerial));
  return files.requestSerial;
}

static uint64_t nextAcquisitionNL(void)
{
  return nextRequestNL();
}

static uint64_t nextDatasetNL(void)
{
  do
    ++files.datasetSerial;
  while (!files.datasetSerial ||
      kvmfrClipboardTransferFromHelper(files.datasetSerial));
  return files.datasetSerial;
}

static fuse_ino_t nextInodeNL(void)
{
  if (++files.inodeSerial <= FUSE_ROOT_ID)
    files.inodeSerial = FUSE_ROOT_ID + 1;
  return files.inodeSerial;
}

static int errnoFromFileError(LG_ClipboardFileError error)
{
  switch (error)
  {
    case LG_CLIPBOARD_FILE_ERROR_NONE:          return 0;
    case LG_CLIPBOARD_FILE_ERROR_NOT_FOUND:     return ENOENT;
    case LG_CLIPBOARD_FILE_ERROR_ACCESS:        return EACCES;
    case LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY: return ENOTDIR;
    case LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY:  return EISDIR;
    case LG_CLIPBOARD_FILE_ERROR_NO_MEMORY:     return ENOMEM;
    case LG_CLIPBOARD_FILE_ERROR_NO_SPACE:      return ENOSPC;
    case LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED: return ENOTSUP;
    case LG_CLIPBOARD_FILE_ERROR_CANCELLED:     return EINTR;
    case LG_CLIPBOARD_FILE_ERROR_STALE:         return ESTALE;
    default:                                       return EIO;
  }
}

static const char * fileErrorName(LG_ClipboardFileError error)
{
  switch (error)
  {
    case LG_CLIPBOARD_FILE_ERROR_NONE:          return "none";
    case LG_CLIPBOARD_FILE_ERROR_NOT_FOUND:     return "not found";
    case LG_CLIPBOARD_FILE_ERROR_ACCESS:        return "access denied";
    case LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY: return "not a directory";
    case LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY:  return "is a directory";
    case LG_CLIPBOARD_FILE_ERROR_IO:            return "I/O error";
    case LG_CLIPBOARD_FILE_ERROR_INVALID:       return "invalid";
    case LG_CLIPBOARD_FILE_ERROR_NO_MEMORY:     return "out of memory";
    case LG_CLIPBOARD_FILE_ERROR_NO_SPACE:      return "no space";
    case LG_CLIPBOARD_FILE_ERROR_DISCONNECTED:  return "disconnected";
    case LG_CLIPBOARD_FILE_ERROR_CANCELLED:     return "cancelled";
    case LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED: return "not supported";
    case LG_CLIPBOARD_FILE_ERROR_STALE:         return "stale";
  }
  return "unknown";
}

static LG_ClipboardFileError fileErrorFromErrno(int error)
{
  switch (error)
  {
    case ENOENT:    return LG_CLIPBOARD_FILE_ERROR_NOT_FOUND;
    case EACCES:
    case EPERM:     return LG_CLIPBOARD_FILE_ERROR_ACCESS;
    case ENOTDIR:   return LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY;
    case EISDIR:    return LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY;
    case ENOMEM:
    case EMFILE:
    case ENFILE:    return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
    case ENOSPC:    return LG_CLIPBOARD_FILE_ERROR_NO_SPACE;
    case ELOOP:
    case ENOTSUP:   return LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
    case ESTALE:    return LG_CLIPBOARD_FILE_ERROR_STALE;
    case ECANCELED: return LG_CLIPBOARD_FILE_ERROR_CANCELLED;
    default:        return LG_CLIPBOARD_FILE_ERROR_IO;
  }
}

static bool grow(void ** 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 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 RemoteAction retireRemoteDatasetNL(
    struct RemoteDataset * dataset)
{
  struct RemoteAction action = { 0 };
  if (!dataset || dataset->current || dataset->presentationPins ||
      dataset->lookupPins || dataset->openPins || dataset->requestPins ||
      dataset->operationPins || dataset->deliveryPending)
    return action;

  if (!dataset->stale)
  {
    dataset->ready     = false;
    dataset->stale     = true;
    action.dataset     = dataset->dataset;
    action.acquisition = dataset->acquisition;
    if (dataset->acquired)
      action.type = REMOTE_ACTION_RELEASE_ACQUISITION;
    else if (dataset->acquiring)
      action.type = REMOTE_ACTION_CANCEL_ACQUISITION;
    dataset->acquiring = false;
    dataset->acquired  = false;
  }
  if (dataset->pruneQueued)
    return action;
  dataset->pruneQueued = true;
  action.object        = dataset;
  return action;
}

/* Retire a failed initial acquisition/list only while it is still the
 * dataset backing the current remote file offer.  The caller must hold
 * files.lock and subsequently call retireRemoteDatasetNL so an acquired
 * dataset is released before it is pruned. */
static uint64_t failCurrentRemoteDatasetNL(
    struct RemoteDataset * dataset)
{
  if (!dataset || !dataset->current)
    return 0;

  dataset->current          = false;
  dataset->ready            = false;
  dataset->deliveryPending  = false;
  dataset->deliveryDeadline = 0;
  if (files.remoteCurrent == dataset)
    files.remoteCurrent = NULL;
  return dataset->dataset;
}

static void pruneRemoteDataset(struct RemoteDataset * dataset)
{
  if (!dataset)
    return;
  LG_LOCK(files.lock);
  if (!dataset->pruneQueued || !dataset->stale || dataset->current ||
      dataset->presentationPins || dataset->lookupPins ||
      dataset->openPins || dataset->requestPins ||
      dataset->operationPins || dataset->deliveryPending)
  {
    dataset->pruneQueued = false;
    LG_UNLOCK(files.lock);
    return;
  }

  struct RemoteDataset ** datasetLink = &files.remoteDatasets;
  while (*datasetLink && *datasetLink != dataset)
    datasetLink = &(*datasetLink)->next;
  if (!*datasetLink)
  {
    LG_UNLOCK(files.lock);
    return;
  }
  *datasetLink = dataset->next;
  if (files.remoteCurrent == dataset)
    files.remoteCurrent = NULL;

  struct RemoteNode * retired = NULL;
  struct RemoteNode ** nodeLink = &files.remoteNodes;
  while (*nodeLink)
  {
    struct RemoteNode * node = *nodeLink;
    if (node->dataset != dataset)
    {
      nodeLink = &node->next;
      continue;
    }
    *nodeLink = node->next;
    node->next = retired;
    retired = node;
  }
  LG_UNLOCK(files.lock);

  while (retired)
  {
    struct RemoteNode * next = retired->next;
    free(retired->name);
    free(retired);
    retired = next;
  }
  free(dataset);
}

static void performRemoteAction(struct RemoteAction action)
{
  switch (action.type)
  {
    case REMOTE_ACTION_CANCEL_ACQUISITION:
      if (!clipboard_fileCancel(action.dataset, action.acquisition,
            LG_CLIPBOARD_FILE_ERROR_CANCELLED))
        DEBUG_WARN("Failed to cancel retired clipboard file acquisition");
      break;

    case REMOTE_ACTION_RELEASE_ACQUISITION:
      if (!clipboard_fileRelease(action.dataset, action.acquisition))
        DEBUG_WARN("Failed to release retired clipboard file acquisition");
      break;

    case REMOTE_ACTION_NONE:
      break;
  }
  pruneRemoteDataset(action.object);
}

static bool pinRemoteOperationNL(
    struct RemoteDataset * dataset, bool consumeDelivery)
{
  if (!dataset || dataset->stale)
    return false;
  ++dataset->operationPins;
  if (consumeDelivery)
  {
    dataset->deliveryPending  = false;
    dataset->deliveryDeadline = 0;
  }
  return true;
}

static void releaseRemoteOperation(struct RemoteDataset * dataset)
{
  if (!dataset)
    return;
  LG_LOCK(files.lock);
  if (dataset->operationPins)
    --dataset->operationPins;
  const struct RemoteAction action = retireRemoteDatasetNL(dataset);
  LG_UNLOCK(files.lock);
  performRemoteAction(action);
}

static void reapRemoteDeliveries(bool pressure)
{
  for (;;)
  {
    const uint64_t now = microtime();
    LG_LOCK(files.lock);
    struct RemoteDataset * candidate = NULL;
    struct RemoteDataset * oldest = NULL;
    unsigned pending = 0;
    for (struct RemoteDataset * dataset = files.remoteDatasets;
        dataset; dataset = dataset->next)
    {
      if (!dataset->deliveryPending)
        continue;
      if (dataset->deliveryDeadline <= now)
      {
        candidate = dataset;
        break;
      }
      if (dataset->current)
        continue;
      ++pending;
      if (!oldest || dataset->deliveryDeadline < oldest->deliveryDeadline)
        oldest = dataset;
    }
    if (!candidate && pressure && pending > FILE_DELIVERY_GRACE_MAX)
      candidate = oldest;
    if (!candidate)
    {
      LG_UNLOCK(files.lock);
      return;
    }
    candidate->deliveryPending  = false;
    candidate->deliveryDeadline = 0;
    const struct RemoteAction action = retireRemoteDatasetNL(candidate);
    LG_UNLOCK(files.lock);
    performRemoteAction(action);
  }
}

static int nextRemoteDeliveryTimeout(void)
{
  const uint64_t now = microtime();
  uint64_t deadline = UINT64_MAX;
  LG_LOCK(files.lock);
  for (struct RemoteDataset * dataset = files.remoteDatasets;
      dataset; dataset = dataset->next)
    if (dataset->deliveryPending &&
        dataset->deliveryDeadline < deadline)
      deadline = dataset->deliveryDeadline;
  LG_UNLOCK(files.lock);
  if (deadline == UINT64_MAX)
    return -1;
  if (deadline <= now)
    return 0;
  const uint64_t delta = deadline - now;
  const uint64_t milliseconds = delta / UINT64_C(1000) +
    (delta % UINT64_C(1000) != 0);
  return milliseconds > INT_MAX ? INT_MAX : (int)milliseconds;
}

static void pruneUnusedRemoteDatasets(void)
{
  for (;;)
  {
    LG_LOCK(files.lock);
    struct RemoteDataset * dataset = files.remoteDatasets;
    while (dataset && (dataset->current || dataset->presentationPins ||
        dataset->lookupPins || dataset->openPins || dataset->requestPins ||
        dataset->operationPins || dataset->deliveryPending ||
        dataset->pruneQueued))
      dataset = dataset->next;
    const struct RemoteAction action = retireRemoteDatasetNL(dataset);
    LG_UNLOCK(files.lock);
    if (!action.object)
      return;
    performRemoteAction(action);
  }
}

static struct RemoteNode * remoteNodeByInodeNL(fuse_ino_t ino)
{
  for (struct RemoteNode * node = files.remoteNodes; node; node = node->next)
    if (node->ino == ino)
      return node;
  return NULL;
}

static struct RemoteNode * remoteChildNL(struct RemoteDataset * dataset,
    fuse_ino_t parent, const char * name)
{
  for (struct RemoteNode * node = files.remoteNodes; node; node = node->next)
    if (node->dataset == dataset && node->parent == parent &&
        !strcmp(node->name, name))
      return node;
  return NULL;
}

static struct RemoteNode * remoteNodeByWireNL(
    struct RemoteDataset * dataset, uint64_t wireNode)
{
  for (struct RemoteNode * node = files.remoteNodes; node; node = node->next)
    if (node->dataset == dataset && node->node == wireNode)
      return node;
  return NULL;
}

static bool validUtf8(const uint8_t * value, size_t length)
{
  for (size_t i = 0; i < length;)
  {
    const uint8_t first = value[i++];
    if (first < 0x80)
      continue;
    unsigned continuation;
    uint32_t codepoint;
    uint32_t minimum;
    if ((first & 0xe0U) == 0xc0U)
    {
      continuation = 1;
      codepoint = first & 0x1fU;
      minimum = 0x80;
    }
    else if ((first & 0xf0U) == 0xe0U)
    {
      continuation = 2;
      codepoint = first & 0x0fU;
      minimum = 0x800;
    }
    else if ((first & 0xf8U) == 0xf0U)
    {
      continuation = 3;
      codepoint = first & 0x07U;
      minimum = 0x10000;
    }
    else
      return false;
    if (continuation > length - i)
      return false;
    while (continuation--)
    {
      const uint8_t next = value[i++];
      if ((next & 0xc0U) != 0x80U)
        return false;
      codepoint = (codepoint << 6) | (next & 0x3fU);
    }
    if (codepoint < minimum || codepoint > 0x10ffffU ||
        (codepoint >= 0xd800U && codepoint <= 0xdfffU))
      return false;
  }
  return true;
}

static struct RemoteRequest * remoteRequestTakeNL(uint64_t request)
{
  struct RemoteRequest ** link = &files.remoteRequests;
  while (*link && (*link)->request.request != request)
    link = &(*link)->next;
  if (!*link)
    return NULL;
  struct RemoteRequest * result = *link;
  *link = result->next;
  result->next = NULL;
  return result;
}

static struct RemoteRequest * remoteRequestTakePointerNL(
    struct RemoteRequest * request)
{
  struct RemoteRequest ** link = &files.remoteRequests;
  while (*link && *link != request)
    link = &(*link)->next;
  if (!*link)
    return NULL;
  *link = request->next;
  request->next = NULL;
  return request;
}

static struct RemoteAction completeRemoteRequestNL(
    struct RemoteRequest * request)
{
  if (request && request->dataset->requestPins)
    --request->dataset->requestPins;
  return request ? retireRemoteDatasetNL(request->dataset) :
    (struct RemoteAction) { 0 };
}

static void freeRemoteRequest(struct RemoteRequest * request)
{
  if (!request)
    return;
  free(request->lookupName);
  free(request->data);
  free(request);
}

static void unregisterRemoteInterrupt(struct RemoteRequest * request)
{
  if (request && request->fuseRequest)
    fuse_req_interrupt_func(request->fuseRequest, NULL, NULL);
}

static void failRemoteRequest(struct RemoteRequest * request, int error)
{
  if (!request)
    return;
  if (request->reply == REMOTE_REPLY_INITIAL)
    DEBUG_WARN("Remote clipboard file root list failed: "
        "dataset=%lu, request=%lu, error=%d (%s)",
        request->request.dataset, request->request.request,
        error, strerror(error));
  unregisterRemoteInterrupt(request);
  if (request->fuseRequest)
    fuse_reply_err(request->fuseRequest, error);
  LG_LOCK(files.lock);
  const uint64_t failedDataset = request->reply == REMOTE_REPLY_INITIAL ?
    failCurrentRemoteDatasetNL(request->dataset) : 0;
  const struct RemoteAction action = completeRemoteRequestNL(request);
  LG_UNLOCK(files.lock);
  freeRemoteRequest(request);
  performRemoteAction(action);
  if (failedDataset)
    clipboard_fileRemoteFailed(failedDataset);
}

static void completeRemoteRequest(struct RemoteRequest * request)
{
  LG_LOCK(files.lock);
  const struct RemoteAction action = completeRemoteRequestNL(request);
  LG_UNLOCK(files.lock);
  freeRemoteRequest(request);
  performRemoteAction(action);
}

static void fillStat(const struct RemoteNode * node, struct stat * value)
{
  memset(value, 0, sizeof(*value));
  value->st_ino   = node->ino;
  value->st_mode  = node->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ?
    S_IFDIR | 0500 : S_IFREG | 0400;
  value->st_nlink = node->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ? 2 : 1;
  value->st_uid   = getuid();
  value->st_gid   = getgid();
  value->st_size  = node->size > INT64_MAX ? INT64_MAX : (off_t)node->size;
  value->st_mtim  = timespecFromNs(node->modifiedNs);
  value->st_ctim  = value->st_mtim;
  value->st_atim  = value->st_mtim;
}

static void replyEntry(fuse_req_t request, struct RemoteNode * node)
{
  struct fuse_entry_param entry =
  {
    .ino           = node->ino,
    .generation    = 1,
    .attr_timeout  = FILE_ATTR_TIMEOUT,
    .entry_timeout = FILE_ENTRY_TIMEOUT,
  };
  fillStat(node, &entry.attr);
  LG_LOCK(files.lock);
  const bool available = !node->dataset->stale;
  if (available)
  {
    ++node->lookups;
    ++node->dataset->lookupPins;
  }
  LG_UNLOCK(files.lock);
  if (!available)
  {
    fuse_reply_err(request, ESTALE);
    return;
  }
  if (fuse_reply_entry(request, &entry) == 0)
    return;

  LG_LOCK(files.lock);
  if (node->lookups)
    --node->lookups;
  if (node->dataset->lookupPins)
    --node->dataset->lookupPins;
  const struct RemoteAction action = retireRemoteDatasetNL(node->dataset);
  LG_UNLOCK(files.lock);
  performRemoteAction(action);
}

static bool installRemoteListNL(struct RemoteRequest * request)
{
  struct RemoteNode * additions = NULL;
  size_t offset = 0;
  while (offset < request->dataSize)
  {
    if (request->dataSize - offset < sizeof(KVMFRClipboardFileEntry))
      goto invalid;
    KVMFRClipboardFileEntry entry;
    memcpy(&entry, request->data + offset, sizeof(entry));
    if (!entry.node || !entry.nameLength || entry.nameLength > NAME_MAX ||
        (entry.type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR &&
         entry.type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY))
      goto invalid;
    const uint64_t bytes64 = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(
        entry.nameLength);
    if (bytes64 > SIZE_MAX || bytes64 > request->dataSize - offset)
      goto invalid;
    const uint8_t * encodedName = request->data + offset +
      sizeof(entry);
    if (memchr(encodedName, 0, entry.nameLength) ||
        !validUtf8(encodedName, entry.nameLength) ||
        (entry.nameLength == 1 && encodedName[0] == '.') ||
        (entry.nameLength == 2 && encodedName[0] == '.' &&
         encodedName[1] == '.') ||
        memchr(encodedName, '/', entry.nameLength))
      goto invalid;
    for (size_t i = sizeof(entry) + entry.nameLength;
        i < (size_t)bytes64; ++i)
      if (request->data[offset + i])
        goto invalid;
    char * name = malloc((size_t)entry.nameLength + 1U);
    if (!name)
      goto invalid;
    memcpy(name, encodedName, entry.nameLength);
    name[entry.nameLength] = 0;

    struct RemoteNode * existing = remoteChildNL(
        request->dataset, request->directory->ino, name);
    struct RemoteNode * wireNode = remoteNodeByWireNL(
        request->dataset, entry.node);
    for (struct RemoteNode * node = additions; node; node = node->next)
    {
      if (node->parent == request->directory->ino &&
          !strcmp(node->name, name))
        existing = node;
      if (node->node == entry.node)
        wireNode = node;
    }

    if (existing || wireNode)
    {
      const bool same = existing && existing == wireNode &&
        existing->size == entry.size &&
        existing->createdNs == entry.createdNs &&
        existing->modifiedNs == entry.modifiedNs &&
        existing->type == entry.type;
      free(name);
      if (!same)
        goto invalid;
    }
    else
    {
      struct RemoteNode * node = calloc(1, sizeof(*node));
      if (!node)
      {
        free(name);
        goto invalid;
      }
      *node = (struct RemoteNode)
      {
        .dataset    = request->dataset,
        .ino        = nextInodeNL(),
        .parent     = request->directory->ino,
        .node       = entry.node,
        .size       = entry.size,
        .createdNs  = entry.createdNs,
        .modifiedNs = entry.modifiedNs,
        .type       = entry.type,
        .name       = name,
        .next       = additions,
      };
      additions = node;
    }
    offset += (size_t)bytes64;
  }
  while (additions)
  {
    struct RemoteNode * node = additions;
    additions = node->next;
    node->next = files.remoteNodes;
    files.remoteNodes = node;
  }
  request->directory->listed = true;
  return true;

invalid:
  while (additions)
  {
    struct RemoteNode * next = additions->next;
    free(additions->name);
    free(additions);
    additions = next;
  }
  return false;
}

static void replyDirectory(struct RemoteRequest * request)
{
  char * buffer = malloc(request->fuseSize ? request->fuseSize : 1U);
  if (!buffer)
  {
    fuse_reply_err(request->fuseRequest, ENOMEM);
    return;
  }
  size_t used = 0;
  off_t index = 0;
  struct stat attr;
  if (request->fuseOffset <= index)
  {
    fillStat(request->directory, &attr);
    const size_t entry = fuse_add_direntry(request->fuseRequest,
        buffer + used, request->fuseSize - used, ".", &attr, index + 1);
    if (entry > request->fuseSize - used)
      goto done;
    used += entry;
  }
  ++index;
  if (request->fuseOffset <= index)
  {
    LG_LOCK(files.lock);
    struct RemoteNode * parent = remoteNodeByInodeNL(
        request->directory->parent);
    fillStat(parent ? parent : request->directory, &attr);
    LG_UNLOCK(files.lock);
    const size_t entry = fuse_add_direntry(request->fuseRequest,
        buffer + used, request->fuseSize - used, "..", &attr, index + 1);
    if (entry > request->fuseSize - used)
      goto done;
    used += entry;
  }
  LG_LOCK(files.lock);
  for (struct RemoteNode * node = files.remoteNodes; node; node = node->next)
  {
    if (node->dataset != request->dataset ||
        node->parent != request->directory->ino)
      continue;
    ++index;
    if (request->fuseOffset > index)
      continue;
    fillStat(node, &attr);
    const size_t entry = fuse_add_direntry(request->fuseRequest,
        buffer + used, request->fuseSize - used,
        node->name, &attr, index + 1);
    if (entry > request->fuseSize - used)
      break;
    used += entry;
  }
  LG_UNLOCK(files.lock);

done:
  fuse_reply_buf(request->fuseRequest, buffer, used);
  free(buffer);
}

static void finishRemoteRequest(struct RemoteRequest * request)
{
  unregisterRemoteInterrupt(request);
  bool installed = true;
  if (request->request.operation == LG_CLIPBOARD_FILE_LIST)
  {
    LG_LOCK(files.lock);
    installed = installRemoteListNL(request);
    if (installed && request->reply == REMOTE_REPLY_INITIAL)
    {
      request->dataset->ready = true;
      const bool current = request->dataset->current;
      const uint64_t datasetId = request->dataset->dataset;
      const uint64_t requestId = request->request.request;
      LG_UNLOCK(files.lock);
      if (current)
      {
        DEBUG_INFO("Remote clipboard file root list ready; publishing: "
            "dataset=%lu, request=%lu",
            datasetId, requestId);
        clipboard_fileRemoteReady(datasetId);
      }
      completeRemoteRequest(request);
      return;
    }
    if (installed && request->reply == REMOTE_REPLY_LOOKUP)
    {
      struct RemoteNode * node = remoteChildNL(request->dataset,
          request->directory->ino, request->lookupName);
      LG_UNLOCK(files.lock);
      if (node)
        replyEntry(request->fuseRequest, node);
      else
        fuse_reply_err(request->fuseRequest, ENOENT);
      completeRemoteRequest(request);
      return;
    }
    LG_UNLOCK(files.lock);
    if (!installed && request->reply == REMOTE_REPLY_INITIAL)
    {
      failRemoteRequest(request, EIO);
      return;
    }
    if (installed && request->reply == REMOTE_REPLY_READDIR)
      replyDirectory(request);
    else if (request->fuseRequest)
      fuse_reply_err(request->fuseRequest, EIO);
  }
  else if (request->reply == REMOTE_REPLY_READ)
  {
    if (request->dataSize > request->request.length)
      fuse_reply_err(request->fuseRequest, EIO);
    else
      fuse_reply_buf(request->fuseRequest,
          (const char *)request->data, request->dataSize);
  }
  completeRemoteRequest(request);
}

static void interruptRemoteRequest(fuse_req_t fuseRequest, void * opaque)
{
  struct RemoteRequest * request = opaque;
  LG_LOCK(files.lock);
  request = remoteRequestTakePointerNL(request);
  const struct RemoteAction action = completeRemoteRequestNL(request);
  LG_UNLOCK(files.lock);
  if (!request)
    return;

  const LG_ClipboardFileRequest descriptor = request->request;
  if (request->transportStarted &&
      !clipboard_fileCancel(descriptor.dataset, descriptor.request,
        LG_CLIPBOARD_FILE_ERROR_CANCELLED))
    DEBUG_WARN("Failed to cancel interrupted clipboard file request");
  fuse_reply_err(fuseRequest, EINTR);
  freeRemoteRequest(request);
  performRemoteAction(action);
}

/* Returns true when another path already took ownership of the request. */
static bool finishUnsentRemoteRequest(uint64_t requestId)
{
  LG_LOCK(files.lock);
  struct RemoteRequest * failed = remoteRequestTakeNL(requestId);
  LG_UNLOCK(files.lock);
  if (!failed)
    return true;
  failRemoteRequest(failed, EIO);
  return false;
}

static bool requestRemote(struct RemoteDataset * dataset,
    struct RemoteNode * node, LG_ClipboardFileOperation operation,
    uint64_t offset, uint32_t length, enum RemoteReply reply,
    fuse_req_t fuseRequest, size_t fuseSize, off_t fuseOffset,
    const char * lookupName)
{
  LG_LOCK(files.lock);
  const bool stale = dataset->stale;
  LG_UNLOCK(files.lock);
  if (stale)
    return false;
  struct RemoteRequest * request = calloc(1, sizeof(*request));
  if (!request)
    return false;
  if (lookupName && !(request->lookupName = strdup(lookupName)))
  {
    free(request);
    return false;
  }
  LG_LOCK(files.lock);
  if (dataset->stale ||
      (reply == REMOTE_REPLY_INITIAL && !dataset->current))
  {
    LG_UNLOCK(files.lock);
    freeRemoteRequest(request);
    return false;
  }
  request->request     = (LG_ClipboardFileRequest)
  {
    .dataset   = dataset->dataset,
    .request   = nextRequestNL(),
    .node      = node->node,
    .offset    = offset,
    .length    = length,
    .operation = operation,
  };
  request->dataset     = dataset;
  request->directory   = node;
  request->reply       = reply;
  request->fuseRequest = fuseRequest;
  request->fuseSize    = fuseSize;
  request->fuseOffset  = fuseOffset;
  request->sizeHint    = KVMFR_CLIPBOARD_SIZE_UNKNOWN;
  request->next        = files.remoteRequests;
  files.remoteRequests = request;
  ++dataset->requestPins;
  LG_UNLOCK(files.lock);
  const LG_ClipboardFileRequest descriptor = request->request;
  if (fuseRequest)
    fuse_req_interrupt_func(fuseRequest, interruptRemoteRequest, request);

  LG_LOCK(files.lock);
  struct RemoteRequest * live = files.remoteRequests;
  while (live && live != request)
    live = live->next;
  const bool sent = live && clipboard_fileRequest(&descriptor);
  if (sent)
    request->transportStarted = true;
  LG_UNLOCK(files.lock);
  if (!live)
    return true;
  if (sent)
    return true;
  return finishUnsentRemoteRequest(descriptor.request);
}

static struct RemoteNode * datasetRootNL(struct RemoteDataset * dataset)
{
  return remoteNodeByInodeNL(dataset->ino);
}

static void fuseLookup(fuse_req_t request, fuse_ino_t parent,
    const char * name)
{
  LG_LOCK(files.lock);
  if (parent == FUSE_ROOT_ID)
  {
    for (struct RemoteDataset * dataset = files.remoteDatasets;
        dataset; dataset = dataset->next)
      if (dataset->ready && !dataset->stale &&
          !strcmp(dataset->name, name))
      {
        struct RemoteNode * node = datasetRootNL(dataset);
        const bool pinned = pinRemoteOperationNL(dataset, true);
        LG_UNLOCK(files.lock);
        if (pinned)
          replyEntry(request, node);
        else
          fuse_reply_err(request, ESTALE);
        releaseRemoteOperation(pinned ? dataset : NULL);
        return;
      }
    LG_UNLOCK(files.lock);
    fuse_reply_err(request, ENOENT);
    return;
  }
  struct RemoteNode * directory = remoteNodeByInodeNL(parent);
  const int error = !directory ? ENOENT :
    directory->dataset->stale ? ESTALE :
    directory->type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ? ENOTDIR : 0;
  if (error)
  {
    LG_UNLOCK(files.lock);
    fuse_reply_err(request, error);
    return;
  }
  struct RemoteNode * child = remoteChildNL(directory->dataset, parent, name);
  const bool listed = directory->listed;
  struct RemoteDataset * dataset = directory->dataset;
  const bool pinned = pinRemoteOperationNL(dataset, false);
  LG_UNLOCK(files.lock);
  if (!pinned)
  {
    fuse_reply_err(request, ESTALE);
    return;
  }
  if (child)
  {
    replyEntry(request, child);
    releaseRemoteOperation(dataset);
    return;
  }
  if (listed)
  {
    fuse_reply_err(request, ENOENT);
    releaseRemoteOperation(dataset);
    return;
  }
  if (!requestRemote(dataset, directory,
        LG_CLIPBOARD_FILE_LIST, 0, 0, REMOTE_REPLY_LOOKUP,
        request, 0, 0, name))
    fuse_reply_err(request, EIO);
  releaseRemoteOperation(dataset);
}

static void fillRootStat(struct stat * value)
{
  memset(value, 0, sizeof(*value));
  value->st_ino   = FUSE_ROOT_ID;
  value->st_mode  = S_IFDIR | 0500;
  value->st_nlink = 2;
  value->st_uid   = getuid();
  value->st_gid   = getgid();
}

static void fuseGetattr(fuse_req_t request, fuse_ino_t ino,
    struct fuse_file_info * info)
{
  (void)info;
  struct stat value;
  if (ino == FUSE_ROOT_ID)
    fillRootStat(&value);
  else
  {
    LG_LOCK(files.lock);
    struct RemoteNode * node = remoteNodeByInodeNL(ino);
    if (node && !node->dataset->stale)
      fillStat(node, &value);
    const bool stale = node && node->dataset->stale;
    const bool found = node && !stale;
    LG_UNLOCK(files.lock);
    if (!found)
    {
      fuse_reply_err(request, stale ? ESTALE : ENOENT);
      return;
    }
  }
  fuse_reply_attr(request, &value, FILE_ATTR_TIMEOUT);
}

static void fuseOpendir(fuse_req_t request, fuse_ino_t ino,
    struct fuse_file_info * info)
{
  struct RemoteDataset * dataset = NULL;
  if (ino != FUSE_ROOT_ID)
  {
    LG_LOCK(files.lock);
    struct RemoteNode * node = remoteNodeByInodeNL(ino);
    const int error = !node ? ENOENT :
      node->dataset->stale ? ESTALE :
      node->type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ? ENOTDIR : 0;
    if (!error)
    {
      dataset = node->dataset;
      ++dataset->openPins;
      info->fh = dataset->acquisition;
    }
    LG_UNLOCK(files.lock);
    if (error)
    {
      fuse_reply_err(request, error);
      return;
    }
  }
  if (fuse_reply_open(request, info) == 0 || !dataset)
    return;
  LG_LOCK(files.lock);
  if (dataset->openPins)
    --dataset->openPins;
  const struct RemoteAction action = retireRemoteDatasetNL(dataset);
  LG_UNLOCK(files.lock);
  performRemoteAction(action);
}

static void replyRootDirectory(fuse_req_t request, size_t size, off_t offset)
{
  char * buffer = malloc(size ? size : 1U);
  if (!buffer)
  {
    fuse_reply_err(request, ENOMEM);
    return;
  }
  size_t used = 0;
  off_t index = 0;
  struct stat attr;
  fillRootStat(&attr);
  const char * fixed[] = { ".", ".." };
  for (unsigned i = 0; i < 2; ++i, ++index)
  {
    if (offset > index)
      continue;
    const size_t entry = fuse_add_direntry(request, buffer + used,
        size - used, fixed[i], &attr, index + 1);
    if (entry > size - used)
      goto done;
    used += entry;
  }
  LG_LOCK(files.lock);
  for (struct RemoteDataset * dataset = files.remoteDatasets;
      dataset; dataset = dataset->next)
  {
    if (!dataset->ready || dataset->stale)
      continue;
    ++index;
    if (offset > index)
      continue;
    struct RemoteNode * node = datasetRootNL(dataset);
    fillStat(node, &attr);
    const size_t entry = fuse_add_direntry(request, buffer + used,
        size - used, dataset->name, &attr, index + 1);
    if (entry > size - used)
      break;
    used += entry;
  }
  LG_UNLOCK(files.lock);

done:
  fuse_reply_buf(request, buffer, used);
  free(buffer);
}

static void fuseReaddir(fuse_req_t request, fuse_ino_t ino,
    size_t size, off_t offset, struct fuse_file_info * info)
{
  if (ino == FUSE_ROOT_ID)
  {
    replyRootDirectory(request, size, offset);
    return;
  }
  LG_LOCK(files.lock);
  struct RemoteNode * directory = remoteNodeByInodeNL(ino);
  const int error = !directory ? ENOENT :
    directory->dataset->stale ||
      info->fh != directory->dataset->acquisition ? ESTALE :
    directory->type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ? ENOTDIR : 0;
  if (error)
  {
    LG_UNLOCK(files.lock);
    fuse_reply_err(request, error);
    return;
  }
  const bool listed = directory->listed;
  struct RemoteDataset * dataset = directory->dataset;
  const bool pinned = pinRemoteOperationNL(dataset, false);
  LG_UNLOCK(files.lock);
  if (!pinned)
  {
    fuse_reply_err(request, ESTALE);
    return;
  }
  if (listed)
  {
    struct RemoteRequest local =
    {
      .dataset     = dataset,
      .directory   = directory,
      .fuseRequest = request,
      .fuseSize    = size,
      .fuseOffset  = offset,
    };
    replyDirectory(&local);
    releaseRemoteOperation(dataset);
    return;
  }
  if (!requestRemote(dataset, directory,
        LG_CLIPBOARD_FILE_LIST, 0, 0, REMOTE_REPLY_READDIR,
        request, size, offset, NULL))
    fuse_reply_err(request, EIO);
  releaseRemoteOperation(dataset);
}

static void fuseOpen(fuse_req_t request, fuse_ino_t ino,
    struct fuse_file_info * info)
{
  LG_LOCK(files.lock);
  struct RemoteNode * node = remoteNodeByInodeNL(ino);
  const int error = !node ? ENOENT :
    node->dataset->stale ? ESTALE :
    node->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ? EISDIR : 0;
  if (error)
  {
    LG_UNLOCK(files.lock);
    fuse_reply_err(request, error);
    return;
  }
  if ((info->flags & O_ACCMODE) != O_RDONLY)
  {
    LG_UNLOCK(files.lock);
    fuse_reply_err(request, EROFS);
    return;
  }
  struct RemoteDataset * dataset = node->dataset;
  ++dataset->openPins;
  info->fh = dataset->acquisition;
  LG_UNLOCK(files.lock);
  info->keep_cache = 1;
  if (fuse_reply_open(request, info) == 0)
    return;
  LG_LOCK(files.lock);
  if (dataset->openPins)
    --dataset->openPins;
  const struct RemoteAction action = retireRemoteDatasetNL(dataset);
  LG_UNLOCK(files.lock);
  performRemoteAction(action);
}

static void fuseRead(fuse_req_t request, fuse_ino_t ino, size_t size,
    off_t offset, struct fuse_file_info * info)
{
  if (offset < 0)
  {
    fuse_reply_err(request, EINVAL);
    return;
  }
  LG_LOCK(files.lock);
  struct RemoteNode * node = remoteNodeByInodeNL(ino);
  const int error = !node ? ENOENT :
    node->dataset->stale ||
      info->fh != node->dataset->acquisition ? ESTALE :
    node->type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR ? EISDIR : 0;
  if (error)
  {
    LG_UNLOCK(files.lock);
    fuse_reply_err(request, error);
    return;
  }
  const uint64_t fileSize = node->size;
  struct RemoteDataset * dataset = node->dataset;
  const bool pinned = pinRemoteOperationNL(dataset, false);
  LG_UNLOCK(files.lock);
  if (!pinned)
  {
    fuse_reply_err(request, ESTALE);
    return;
  }
  if ((uint64_t)offset >= fileSize || !size)
  {
    fuse_reply_buf(request, NULL, 0);
    releaseRemoteOperation(dataset);
    return;
  }
  uint64_t wanted = fileSize - (uint64_t)offset;
  if (wanted > size)
    wanted = size;
  if (wanted > FILE_REQUEST_MAX)
    wanted = FILE_REQUEST_MAX;
  if (!requestRemote(dataset, node, LG_CLIPBOARD_FILE_READ,
        (uint64_t)offset, (uint32_t)wanted, REMOTE_REPLY_READ,
        request, 0, 0, NULL))
    fuse_reply_err(request, EIO);
  releaseRemoteOperation(dataset);
}

static void fuseStatfs(fuse_req_t request, fuse_ino_t ino)
{
  (void)ino;
  struct statvfs value;
  memset(&value, 0, sizeof(value));
  value.f_bsize   = 4096;
  value.f_frsize  = 4096;
  value.f_namemax = NAME_MAX;
  value.f_flag    = ST_RDONLY | ST_NOSUID;
  fuse_reply_statfs(request, &value);
}

static void fuseAccess(fuse_req_t request, fuse_ino_t ino, int mask)
{
  if (mask & W_OK)
  {
    fuse_reply_err(request, EROFS);
    return;
  }
  if (ino == FUSE_ROOT_ID)
  {
    fuse_reply_err(request, 0);
    return;
  }
  LG_LOCK(files.lock);
  struct RemoteNode * node = remoteNodeByInodeNL(ino);
  const bool found = node && !node->dataset->stale;
  const bool stale = node && node->dataset->stale;
  LG_UNLOCK(files.lock);
  fuse_reply_err(request, found ? 0 : stale ? ESTALE : ENOENT);
}

static void forgetRemoteNode(fuse_ino_t ino, uint64_t count)
{
  LG_LOCK(files.lock);
  struct RemoteNode * node = remoteNodeByInodeNL(ino);
  struct RemoteAction action = { 0 };
  if (node)
  {
    if (count > node->lookups)
      count = node->lookups;
    node->lookups -= count;
    if (count > node->dataset->lookupPins)
      node->dataset->lookupPins = 0;
    else
      node->dataset->lookupPins -= count;
    action = retireRemoteDatasetNL(node->dataset);
  }
  LG_UNLOCK(files.lock);
  performRemoteAction(action);
}

static void fuseForget(fuse_req_t request, fuse_ino_t ino,
    uint64_t count)
{
  if (ino != FUSE_ROOT_ID)
    forgetRemoteNode(ino, count);
  fuse_reply_none(request);
}

static void fuseForgetMulti(fuse_req_t request, size_t count,
    struct fuse_forget_data * forgets)
{
  for (size_t i = 0; i < count; ++i)
    if (forgets[i].ino != FUSE_ROOT_ID)
      forgetRemoteNode(forgets[i].ino, forgets[i].nlookup);
  fuse_reply_none(request);
}

static void releaseRemoteHandle(fuse_req_t request,
    struct fuse_file_info * info)
{
  if (!info->fh)
  {
    fuse_reply_err(request, 0);
    return;
  }
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset = remoteDatasetByAcquisitionNL(info->fh);
  const bool found = dataset != NULL;
  if (dataset && dataset->openPins)
    --dataset->openPins;
  const struct RemoteAction action = retireRemoteDatasetNL(dataset);
  LG_UNLOCK(files.lock);
  fuse_reply_err(request, found ? 0 : ESTALE);
  performRemoteAction(action);
}

static void fuseRelease(fuse_req_t request, fuse_ino_t ino,
    struct fuse_file_info * info)
{
  (void)ino;
  releaseRemoteHandle(request, info);
}

static void fuseReleasedir(fuse_req_t request, fuse_ino_t ino,
    struct fuse_file_info * info)
{
  (void)ino;
  releaseRemoteHandle(request, info);
}

static void fuseInit(void * userData, struct fuse_conn_info * connection)
{
  (void)userData;
  connection->max_read = FILE_REQUEST_MAX;
}

static const struct fuse_lowlevel_ops fuseOps =
{
  .init         = fuseInit,
  .lookup       = fuseLookup,
  .forget       = fuseForget,
  .forget_multi = fuseForgetMulti,
  .getattr      = fuseGetattr,
  .opendir      = fuseOpendir,
  .readdir      = fuseReaddir,
  .releasedir   = fuseReleasedir,
  .open         = fuseOpen,
  .read         = fuseRead,
  .release      = fuseRelease,
  .access       = fuseAccess,
  .statfs       = fuseStatfs,
};

enum FuseWaitResult
{
  FUSE_WAIT_ERROR,
  FUSE_WAIT_SESSION,
  FUSE_WAIT_WAKE,
  FUSE_WAIT_TIMEOUT,
};

static enum FuseWaitResult waitFuseEvent(
    int sessionFd, int stopFd, int timeout)
{
  struct pollfd descriptors[] =
  {
    { .fd = sessionFd, .events = POLLIN },
    { .fd = stopFd,    .events = POLLIN },
  };
  int result;
  do
    result = poll(descriptors,
        sizeof(descriptors) / sizeof(*descriptors), timeout);
  while (result < 0 && errno == EINTR);
  if (result < 0)
    return FUSE_WAIT_ERROR;
  if (!result)
    return FUSE_WAIT_TIMEOUT;
  if (descriptors[1].revents & POLLIN)
  {
    uint64_t value;
    ssize_t received;
    do
      received = read(stopFd, &value, sizeof(value));
    while (received < 0 && errno == EINTR);
    if (received != (ssize_t)sizeof(value) &&
        !(received < 0 && errno == EAGAIN))
      return FUSE_WAIT_ERROR;
    return FUSE_WAIT_WAKE;
  }
  if (descriptors[1].revents)
  {
    errno = EIO;
    return FUSE_WAIT_ERROR;
  }
  if (descriptors[0].revents & POLLIN)
    return FUSE_WAIT_SESSION;
  errno = EIO;
  return FUSE_WAIT_ERROR;
}

static bool wakeFuseThread(int stopFd)
{
  const uint64_t value = 1;
  ssize_t result;
  do
    result = write(stopFd, &value, sizeof(value));
  while (result < 0 && errno == EINTR);
  return result == (ssize_t)sizeof(value) ||
    (result < 0 && errno == EAGAIN);
}

static int fuseThread(void * opaque)
{
  struct fuse_session * session = opaque;
  struct fuse_buf buffer =
  {
    .mem = NULL,
  };
  int result = 0;
  while (!fuse_session_exited(session))
  {
    switch (waitFuseEvent(fuse_session_fd(session), files.fuseStopFd,
          nextRemoteDeliveryTimeout()))
    {
      case FUSE_WAIT_WAKE:
      {
        LG_LOCK(files.lock);
        const bool stopping = files.stopping;
        LG_UNLOCK(files.lock);
        if (stopping)
          goto done;
        reapRemoteDeliveries(false);
        continue;
      }

      case FUSE_WAIT_TIMEOUT:
        reapRemoteDeliveries(false);
        continue;

      case FUSE_WAIT_ERROR:
        result = -errno;
        goto done;

      case FUSE_WAIT_SESSION:
        break;
    }

    result = fuse_session_receive_buf(session, &buffer);
    if (result == -EINTR)
      continue;
    if (result <= 0)
      goto done;
    fuse_session_process_buf(session, &buffer);
  }

done:
  free(buffer.mem);
  if (result > 0)
    result = 0;
  fuse_session_reset(session);
  return result;
}

static char * uriDecode(const char * value, size_t length)
{
  static const char hex[] = "0123456789abcdef";
  char * result = malloc(length + 1U);
  if (!result)
    return NULL;
  size_t out = 0;
  for (size_t i = 0; i < length; ++i)
  {
    unsigned char ch = (unsigned char)value[i];
    if (ch == '%')
    {
      if (i + 2U >= length)
        goto invalid;
      const char * high = strchr(hex, (char)(value[i + 1U] | 0x20));
      const char * low  = strchr(hex, (char)(value[i + 2U] | 0x20));
      if (!high || !low)
        goto invalid;
      ch = (unsigned char)(((high - hex) << 4) | (low - hex));
      i += 2U;
    }
    if (!ch)
      goto invalid;
    result[out++] = (char)ch;
  }
  result[out] = 0;
  return result;

invalid:
  free(result);
  return NULL;
}

static bool localNodeStat(struct LocalNode * node, const struct stat * value)
{
  if (S_ISREG(value->st_mode))
    node->type = KVMFR_CLIPBOARD_FILE_TYPE_REGULAR;
  else if (S_ISDIR(value->st_mode))
    node->type = KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY;
  else
    return false;
  node->size       = value->st_size < 0 ? 0 : (uint64_t)value->st_size;
  node->modifiedNs = nsFromTimespec(value->st_mtim);
  node->createdNs  = 0;
  node->device     = value->st_dev;
  node->inode      = value->st_ino;
  return true;
}

static bool localStatSupported(const struct stat * value)
{
  return S_ISREG(value->st_mode) || S_ISDIR(value->st_mode);
}

static bool localStatEqual(const struct stat * first,
    const struct stat * second)
{
  return first->st_dev == second->st_dev &&
    first->st_ino == second->st_ino &&
    ((S_ISREG(first->st_mode) && S_ISREG(second->st_mode)) ||
     (S_ISDIR(first->st_mode) && S_ISDIR(second->st_mode))) &&
    first->st_size == second->st_size &&
    first->st_mtim.tv_sec == second->st_mtim.tv_sec &&
    first->st_mtim.tv_nsec == second->st_mtim.tv_nsec;
}

static bool localNodeMatches(const struct LocalNode * node,
    const struct stat * value)
{
  return node->device == value->st_dev && node->inode == value->st_ino &&
    ((node->type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR &&
      S_ISREG(value->st_mode)) ||
     (node->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY &&
      S_ISDIR(value->st_mode))) &&
    node->size == (value->st_size < 0 ? 0 : (uint64_t)value->st_size) &&
    node->modifiedNs == nsFromTimespec(value->st_mtim);
}

static int openAbsoluteLocal(const char * path, char ** name,
    struct stat * value)
{
  if (!path || path[0] != '/' || !path[1])
    return -1;

  int current = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  if (current < 0)
    return -1;

  const char * component = path + 1;
  for (;;)
  {
    const char * slash = strchr(component, '/');
    const size_t length = slash ? (size_t)(slash - component) :
      strlen(component);
    if (!length || length > NAME_MAX ||
        (length == 1U && component[0] == '.') ||
        (length == 2U && component[0] == '.' && component[1] == '.'))
      goto failed;

    char * part = strndup(component, length);
    if (!part)
      goto failed;

    struct stat before;
    const bool final = !slash;
    int next = -1;
    if (fstatat(current, part, &before, AT_SYMLINK_NOFOLLOW) == 0 &&
        !S_ISLNK(before.st_mode) &&
        (final ? localStatSupported(&before) : S_ISDIR(before.st_mode)))
    {
      int flags = O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK;
      if (!final || S_ISDIR(before.st_mode))
        flags |= O_DIRECTORY;
      next = openat(current, part, flags);
    }
    if (next < 0)
    {
      free(part);
      goto failed;
    }

    struct stat after;
    if (fstat(next, &after) < 0 || !localStatEqual(&before, &after))
    {
      close(next);
      free(part);
      goto failed;
    }
    close(current);
    current = next;

    if (final)
    {
      *name  = part;
      *value = after;
      return current;
    }
    free(part);
    component = slash + 1;
  }

failed:
  close(current);
  return -1;
}

static int openLocalNode(const struct LocalNode * node,
    struct stat * value)
{
  if (!node || !node->root || node->root->fd < 0)
    return -1;

  int current;
  if (node->root->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY)
    current = openat(node->root->fd, ".",
        O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
  else
    current = fcntl(node->root->fd, F_DUPFD_CLOEXEC, 0);
  if (current < 0)
    return -1;

  const char * component = node->path;
  while (*component)
  {
    const char * slash = strchr(component, '/');
    const size_t length = slash ? (size_t)(slash - component) :
      strlen(component);
    if (!length || length > NAME_MAX ||
        (length == 1U && component[0] == '.') ||
        (length == 2U && component[0] == '.' && component[1] == '.'))
      goto failed;
    char * part = strndup(component, length);
    if (!part)
      goto failed;
    int flags = O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK;
    if (slash || node->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY)
      flags |= O_DIRECTORY;
    const int next = openat(current, part, flags);
    free(part);
    if (next < 0)
      goto failed;
    close(current);
    current = next;
    component = slash ? slash + 1 : component + length;
  }

  if (fstat(current, value) < 0)
    goto failed;
  if (!localNodeMatches(node, value))
  {
    errno = ESTALE;
    goto failed;
  }
  return current;

failed:
  close(current);
  return -1;
}

static void freeLocalDataset(struct LocalDataset * dataset)
{
  if (!dataset)
    return;
  struct LocalNode * node = dataset->nodes;
  while (node)
  {
    struct LocalNode * next = node->next;
    if (node->fd >= 0)
      close(node->fd);
    free(node->path);
    free(node->name);
    free(node);
    node = next;
  }
  free(dataset);
}

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 char * uriPath(const char * line, size_t length)
{
  if (length < strlen(FILE_URI_PREFIX) ||
      strncasecmp(line, FILE_URI_PREFIX, strlen(FILE_URI_PREFIX)))
    return NULL;
  line   += strlen(FILE_URI_PREFIX);
  length -= strlen(FILE_URI_PREFIX);

  if (length >= 10U && !strncasecmp(line, "localhost/", 10U))
  {
    line += 9U;
    length -= 9U;
  }
  else if (!length || line[0] != '/')
    return NULL;

  static const char punctuation[] = "-._~!$&'()*+,;=:@/%";
  for (size_t i = 0; i < length; ++i)
  {
    const unsigned char ch = (unsigned char)line[i];
    if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
          (ch >= '0' && ch <= '9') || strchr(punctuation, ch)))
      return NULL;
  }
  char * path = uriDecode(line, length);
  if (!path)
    return NULL;
  size_t pathLength = strlen(path);
  while (pathLength > 1U && path[pathLength - 1U] == '/')
    path[--pathLength] = 0;
  return path;
}

static struct LocalNode * localNodeByIdNL(struct LocalDataset * dataset,
    uint64_t node)
{
  for (struct LocalNode * item = dataset->nodes; item; item = item->next)
    if (item->node == node)
      return item;
  return NULL;
}

static struct LocalNode * localChildNL(struct LocalDataset * dataset,
    uint64_t parent, const char * name)
{
  for (struct LocalNode * item = dataset->nodes; item; item = item->next)
    if (item->parent == parent && !strcmp(item->name, name))
      return item;
  return NULL;
}

static struct LocalNode * addLocalNode(struct LocalDataset * dataset,
    uint64_t parent, struct LocalNode * root, char * path,
    const char * name, const struct stat * value)
{
  struct LocalNode * node = calloc(1, sizeof(*node));
  if (!node)
  {
    free(path);
    return NULL;
  }
  node->fd   = -1;
  node->path = path;
  node->name = strdup(name);
  if (!node->name || !localNodeStat(node, value))
  {
    free(node->path);
    free(node->name);
    free(node);
    return NULL;
  }
  node->dataset  = dataset;
  node->root     = root ? root : node;
  node->parent   = parent;
  node->node     = ++dataset->nodeSerial;
  node->next     = dataset->nodes;
  dataset->nodes = node;
  return node;
}

bool clipboardFiles_setLocal(const char * mime,
    const void * data, size_t size)
{
  if (!mime || (!data && size))
    return false;

  const bool gnome_or_mate =
    !strcmp(mime, FILE_GNOME_MIME) ||
    !strcmp(mime, FILE_MATE_MIME );

  if (strcmp(mime, FILE_URI_MIME) && !gnome_or_mate)
    return false;

  const char * text = data;
  size_t offset = 0;
  if (gnome_or_mate)
  {
    const char * newline = size ? memchr(text, '\n', size) : NULL;
    if (!newline)
      return false;
    const size_t operationLength = (size_t)(newline - text);
    if ((operationLength != 4U || memcmp(text, "copy", 4U)) &&
        (operationLength != 3U || memcmp(text, "cut", 3U)))
      return false;
    offset = operationLength + 1U;
  }
  struct LocalDataset * dataset = calloc(1, sizeof(*dataset));
  if (!dataset)
    return false;
  while (offset < size)
  {
    const char * line = text + offset;
    const char * newline = memchr(line, '\n', size - offset);
    size_t length = newline ? (size_t)(newline - line) : size - offset;
    if (length && line[length - 1U] == '\r')
      --length;
    offset += (newline ? (size_t)(newline - line) + 1U : size - offset);
    if (!length || line[0] == '#')
      continue;
    char * path = uriPath(line, length);
    if (!path)
      goto invalid;
    char * name = NULL;
    struct stat value;
    const int fd = openAbsoluteLocal(path, &name, &value);
    free(path);
    if (fd < 0)
    {
      free(name);
      goto invalid;
    }
    char * relative = strdup("");
    if (!relative || !validUtf8((const uint8_t *)name, strlen(name)) ||
        localChildNL(dataset, KVMFR_CLIPBOARD_FILE_ROOT_NODE, name))
    {
      close(fd);
      free(relative);
      free(name);
      goto invalid;
    }
    struct LocalNode * node = addLocalNode(dataset,
        KVMFR_CLIPBOARD_FILE_ROOT_NODE, NULL, relative, name, &value);
    free(name);
    if (!node)
    {
      close(fd);
      goto invalid;
    }
    node->fd = fd;
  }
  if (!dataset->nodes)
    goto invalid;
  dataset->current = true;
  LG_LOCK(files.lock);
  dataset->dataset = nextDatasetNL();
  struct LocalDataset * retired = NULL;
  if (files.localCurrent)
  {
    files.localCurrent->current = false;
    retired = takeUnusedLocalDatasetNL(files.localCurrent);
  }
  dataset->next       = files.localDatasets;
  files.localDatasets = dataset;
  files.localCurrent  = dataset;
  const uint64_t datasetId = dataset->dataset;
  LG_UNLOCK(files.lock);
  freeLocalDataset(retired);
  clipboard_notifyFiles(datasetId);
  return true;

invalid:
  freeLocalDataset(dataset);
  return false;
}

void clipboardFiles_clearLocal(void)
{
  LG_LOCK(files.lock);
  struct LocalDataset * retired = NULL;
  if (files.localCurrent)
  {
    files.localCurrent->current = false;
    retired = takeUnusedLocalDatasetNL(files.localCurrent);
  }
  files.localCurrent = NULL;
  LG_UNLOCK(files.lock);
  freeLocalDataset(retired);
}

static char * uriEncodePath(const char * path)
{
  static const char hex[] = "0123456789ABCDEF";
  const size_t length = strlen(path);
  if (length > (SIZE_MAX - 1U) / 3U)
    return NULL;
  char * result = malloc(length * 3U + 1U);
  if (!result)
    return NULL;
  size_t out = 0;
  for (size_t i = 0; i < length; ++i)
  {
    const unsigned char ch = (unsigned char)path[i];
    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
        (ch >= '0' && ch <= '9') || ch == '/' || ch == '-' ||
        ch == '_' || ch == '.' || ch == '~')
      result[out++] = (char)ch;
    else
    {
      result[out++] = '%';
      result[out++] = hex[ch >> 4];
      result[out++] = hex[ch & 0xfU];
    }
  }
  result[out] = 0;
  return result;
}

static bool getRemoteDatasetNL(struct RemoteDataset * dataset,
    const char * mime, char ** data, size_t * size)
{
  if (!strcmp(mime, FILE_KDE_CUT_MIME))
  {
    *data = strdup("0");
    *size = *data ? 1U : 0U;
    return *data != NULL;
  }

  const bool gnome_or_mate =
    !strcmp(mime, FILE_GNOME_MIME) ||
    !strcmp(mime, FILE_MATE_MIME );

  if (strcmp(mime, FILE_URI_MIME) && !gnome_or_mate)
    return false;

  char * result = NULL;
  size_t used = 0;
  size_t capacity = 0;
  if (gnome_or_mate)
  {
    if (!grow((void **)&result, &capacity, 5U))
      return false;
    memcpy(result, "copy", 4U);
    used = 4U;
  }
  for (struct RemoteNode * node = files.remoteNodes; node; node = node->next)
  {
    if (node->dataset != dataset || node->parent != dataset->ino)
      continue;
    const size_t rawLength = strlen(files.mountpoint) + 1U +
      strlen(dataset->name) + 1U + strlen(node->name) + 1U;
    char * raw = malloc(rawLength);
    if (!raw)
      goto error;
    snprintf(raw, rawLength, "%s/%s/%s",
        files.mountpoint, dataset->name, node->name);
    char * encoded = uriEncodePath(raw);
    free(raw);
    if (!encoded)
      goto error;

    const size_t line = strlen(FILE_URI_PREFIX) + strlen(encoded) + 2U;
    if (!grow((void **)&result, &capacity, used + line + 1U))
    {
      free(encoded);
      goto error;
    }

    if (gnome_or_mate)
      used += (size_t)snprintf(result + used, capacity - used,
          "\n" FILE_URI_PREFIX "%s", encoded);
    else
      used += (size_t)snprintf(result + used, capacity - used,
          FILE_URI_PREFIX "%s\r\n", encoded);
    free(encoded);
  }
  if (!result)
    return false;
  *data = result;
  *size = used;
  return true;

error:
  free(result);
  return false;
}

bool clipboardFiles_getRemote(const char * mime,
    char ** data, size_t * size)
{
  if (!mime || !data || !size)
    return false;
  *data = NULL;
  *size = 0;
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset = files.remoteCurrent;
  const bool result = dataset && dataset->ready && !dataset->stale &&
    getRemoteDatasetNL(dataset, mime, data, size);
  LG_UNLOCK(files.lock);
  return result;
}

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 clipboardFiles_getRemotePresentation(uint64_t presentation,
    const char * mime, char ** data, size_t * size)
{
  if (!presentation || !mime || !data || !size)
    return false;
  *data = NULL;
  *size = 0;
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset = remoteDatasetByAcquisitionNL(presentation);
  const bool result = dataset && dataset->presentationPins &&
    dataset->ready && !dataset->stale &&
    getRemoteDatasetNL(dataset, mime, data, size);
  LG_UNLOCK(files.lock);
  return result;
}

void clipboardFiles_remotePresentationDelivered(uint64_t presentation)
{
  if (!presentation)
    return;
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset =
    remoteDatasetByAcquisitionNL(presentation);
  const bool delivered = dataset && dataset->presentationPins &&
    dataset->ready && dataset->acquired && !dataset->stale;
  int wakeFd = -1;
  if (delivered)
  {
    const uint64_t now = microtime();
    dataset->deliveryDeadline = now >
        UINT64_MAX - FILE_DELIVERY_GRACE_US ? UINT64_MAX :
      now + FILE_DELIVERY_GRACE_US;
    dataset->deliveryPending  = true;
    wakeFd                    = files.fuseStopFd;
  }
  LG_UNLOCK(files.lock);
  if (wakeFd >= 0 && !wakeFuseThread(wakeFd))
    DEBUG_WARN("Failed to schedule clipboard file delivery expiry: %s",
        strerror(errno));
  if (delivered)
    reapRemoteDeliveries(true);
}

void clipboardFiles_remotePresentationRelease(uint64_t presentation)
{
  if (!presentation)
    return;
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset = remoteDatasetByAcquisitionNL(presentation);
  if (dataset && dataset->presentationPins)
    --dataset->presentationPins;
  const struct RemoteAction 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 result = found && found->ready && found->current;
  LG_UNLOCK(files.lock);
  return result;
}

static struct RemoteRequest * takeInitialRequestNL(
    struct RemoteDataset * dataset)
{
  struct RemoteRequest * request = files.remoteRequests;
  while (request && (request->dataset != dataset ||
      request->reply != REMOTE_REPLY_INITIAL))
    request = request->next;
  return request ? remoteRequestTakePointerNL(request) : NULL;
}

static void cancelRetiredInitialRequest(struct RemoteRequest * request)
{
  if (!request)
    return;
  const LG_ClipboardFileRequest descriptor = request->request;
  if (request->transportStarted &&
      !clipboard_fileCancel(descriptor.dataset, descriptor.request,
        LG_CLIPBOARD_FILE_ERROR_CANCELLED))
    DEBUG_WARN("Failed to cancel retired clipboard file request");
  unregisterRemoteInterrupt(request);
  completeRemoteRequest(request);
}

bool clipboardFiles_remoteOffer(uint64_t datasetId)
{
  DEBUG_INFO("dataset=%lu",
      datasetId);
  if (!datasetId)
    return false;
  struct RemoteDataset * dataset = calloc(1, sizeof(*dataset));
  struct RemoteNode * root = calloc(1, sizeof(*root));
  if (!dataset || !root)
  {
    free(dataset);
    free(root);
    return false;
  }
  LG_LOCK(files.lock);
  if (files.remoteCurrent && files.remoteCurrent->dataset == datasetId &&
      !files.remoteCurrent->stale)
  {
    LG_UNLOCK(files.lock);
    free(dataset);
    free(root);
    return true;
  }
  struct RemoteDataset * reusable = remoteDatasetByIdNL(datasetId);
  if (reusable && reusable->stale)
    reusable = NULL;
  if (!reusable)
  {
    dataset->dataset     = datasetId;
    dataset->acquisition = nextAcquisitionNL();
    dataset->ino         = nextInodeNL();
    dataset->current     = true;
    dataset->acquiring   = true;
    snprintf(dataset->name, sizeof(dataset->name),
        "%016" PRIx64 "-%016" PRIx64,
        datasetId, (uint64_t)dataset->ino);
    *root = (struct RemoteNode)
    {
      .dataset = dataset,
      .ino     = dataset->ino,
      .parent  = FUSE_ROOT_ID,
      .node    = KVMFR_CLIPBOARD_FILE_ROOT_NODE,
      .type    = KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY,
      .name    = strdup(dataset->name),
    };
    if (!root->name)
    {
      LG_UNLOCK(files.lock);
      free(dataset);
      free(root);
      return false;
    }
  }
  struct RemoteDataset * previous = files.remoteCurrent;
  struct RemoteRequest * cancelled = NULL;
  struct RemoteAction previousAction = { 0 };
  if (previous)
  {
    previous->current = false;
    cancelled = takeInitialRequestNL(previous);
    previousAction = retireRemoteDatasetNL(previous);
  }
  if (reusable)
  {
    reusable->current   = true;
    files.remoteCurrent = reusable;
    LG_UNLOCK(files.lock);
    cancelRetiredInitialRequest(cancelled);
    performRemoteAction(previousAction);
    reapRemoteDeliveries(true);
    free(dataset);
    free(root);
    return true;
  }
  dataset->next        = files.remoteDatasets;
  files.remoteDatasets = dataset;
  files.remoteCurrent  = dataset;
  root->next           = files.remoteNodes;
  files.remoteNodes    = root;
  const uint64_t acquisition = dataset->acquisition;
  LG_UNLOCK(files.lock);
  cancelRetiredInitialRequest(cancelled);
  performRemoteAction(previousAction);
  reapRemoteDeliveries(true);
  if (!clipboard_fileAcquire(datasetId, acquisition))
  {
    clipboardFiles_remoteCancel(datasetId, acquisition,
        LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
    return false;
  }
  return true;
}

void clipboardFiles_remoteClear(void)
{
  LG_LOCK(files.lock);
  struct RemoteDataset * previous = files.remoteCurrent;
  struct RemoteRequest * cancelled = NULL;
  struct RemoteAction action = { 0 };
  if (previous)
  {
    previous->current = false;
    cancelled = takeInitialRequestNL(previous);
    action = retireRemoteDatasetNL(previous);
  }
  files.remoteCurrent = NULL;
  LG_UNLOCK(files.lock);
  cancelRetiredInitialRequest(cancelled);
  performRemoteAction(action);
}

void clipboardFiles_providerUnavailable(void)
{
  if (!files.initialized)
    return;

  LG_LOCK(files.lock);
  files.remoteCurrent = NULL;
  for (struct RemoteDataset * dataset = files.remoteDatasets;
      dataset; dataset = dataset->next)
  {
    dataset->current          = false;
    dataset->acquiring        = false;
    dataset->acquired         = false;
    dataset->stale            = true;
    dataset->deliveryPending  = false;
    dataset->deliveryDeadline = 0;
  }
  struct RemoteRequest * remoteRequests = files.remoteRequests;
  files.remoteRequests = NULL;

  struct LocalAcquisition * localAcquisitions = files.localAcquisitions;
  files.localAcquisitions = NULL;
  for (struct LocalAcquisition * acquisition = localAcquisitions;
      acquisition; acquisition = acquisition->next)
    if (acquisition->dataset->references)
      --acquisition->dataset->references;

  struct LocalDataset * retired = NULL;
  struct LocalDataset ** datasetLink = &files.localDatasets;
  while (*datasetLink)
  {
    struct LocalDataset * dataset = *datasetLink;
    if (dataset->current || dataset->references)
    {
      datasetLink = &dataset->next;
      continue;
    }
    *datasetLink = dataset->next;
    dataset->next = retired;
    retired = dataset;
  }

  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);
  }
  LG_UNLOCK(files.lock);
  if (files.jobEvent)
    lgSignalEvent(files.jobEvent);

  while (remoteRequests)
  {
    struct RemoteRequest * next = remoteRequests->next;
    unregisterRemoteInterrupt(remoteRequests);
    if (remoteRequests->fuseRequest)
      fuse_reply_err(remoteRequests->fuseRequest, EIO);
    completeRemoteRequest(remoteRequests);
    remoteRequests = next;
  }
  pruneUnusedRemoteDatasets();
  while (localAcquisitions)
  {
    struct LocalAcquisition * next = localAcquisitions->next;
    free(localAcquisitions);
    localAcquisitions = next;
  }
  while (retired)
  {
    struct LocalDataset * next = retired->next;
    freeLocalDataset(retired);
    retired = next;
  }
}

void clipboardFiles_remoteAcquired(uint64_t datasetId,
    uint64_t acquisition, LG_ClipboardFileError error)
{
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset = remoteDatasetByAcquisitionNL(acquisition);
  const bool valid = dataset && dataset->dataset == datasetId &&
    dataset->acquiring && !dataset->stale;
  if (valid)
  {
    dataset->acquiring = false;
    dataset->acquired  = !error;
  }
  struct RemoteNode * root = valid ? datasetRootNL(dataset) : NULL;
  const bool pinned = valid && !error && root &&
    pinRemoteOperationNL(dataset, false);
  const uint64_t failedDataset = valid && (error || !pinned) ?
    failCurrentRemoteDatasetNL(dataset) : 0;
  const struct RemoteAction action = valid ?
    retireRemoteDatasetNL(dataset) : (struct RemoteAction) { 0 };
  LG_UNLOCK(files.lock);
  if (valid && error)
    DEBUG_WARN("failed: dataset=%lu, acquisition=%lu, error=%u (%s)",
        datasetId, acquisition, (unsigned)error, fileErrorName(error));
  else if (valid)
    DEBUG_INFO("dataset=%lu, acquisition=%lu", datasetId, acquisition);
  performRemoteAction(action);
  if (failedDataset)
    clipboard_fileRemoteFailed(failedDataset);
  if (!pinned)
    return;
  if (!requestRemote(dataset, root, LG_CLIPBOARD_FILE_LIST,
        0, 0, REMOTE_REPLY_INITIAL, NULL, 0, 0, NULL))
  {
    LG_LOCK(files.lock);
    const uint64_t unsentDataset =
      failCurrentRemoteDatasetNL(dataset);
    const struct RemoteAction failed = retireRemoteDatasetNL(dataset);
    LG_UNLOCK(files.lock);
    performRemoteAction(failed);
    if (unsentDataset)
      clipboard_fileRemoteFailed(unsentDataset);
  }
  releaseRemoteOperation(dataset);
}

static bool sameRemoteFileRequest(
    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;
}

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 = files.remoteRequests;
  while (request && request->request.request != descriptor->request)
    request = request->next;
  const bool valid = request && !request->began &&
    sameRemoteFileRequest(request, descriptor) &&
    (descriptor->operation != LG_CLIPBOARD_FILE_READ ||
     sizeHint == descriptor->length);
  if (valid)
  {
    request->began    = true;
    request->sizeHint = sizeHint;
  }
  struct RemoteRequest * failed = !valid && request ?
    remoteRequestTakeNL(descriptor->request) : NULL;
  LG_UNLOCK(files.lock);
  failRemoteRequest(failed, EIO);
  return valid ? LG_CLIPBOARD_RESULT_ACCEPTED :
    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 bool envelopeValid = data && size && responseOffset <= SIZE_MAX &&
    size <= SIZE_MAX - (size_t)responseOffset;
  LG_LOCK(files.lock);
  struct RemoteRequest * request = files.remoteRequests;
  while (request && request->request.request != descriptor->request)
    request = request->next;
  const size_t end = envelopeValid ? (size_t)responseOffset + size : 0;
  const bool valid = envelopeValid && request && request->began &&
    sameRemoteFileRequest(request, descriptor) &&
    responseOffset == request->dataSize &&
    (request->sizeHint == KVMFR_CLIPBOARD_SIZE_UNKNOWN ||
     end <= request->sizeHint) &&
    (descriptor->operation != LG_CLIPBOARD_FILE_READ ||
     end <= descriptor->length) &&
    grow((void **)&request->data, &request->dataCapacity, end);
  if (valid)
  {
    memcpy(request->data + request->dataSize, data, size);
    request->dataSize = end;
  }
  struct RemoteRequest * failed = !valid && request ?
    remoteRequestTakeNL(descriptor->request) : NULL;
  LG_UNLOCK(files.lock);
  failRemoteRequest(failed, EIO);
  return valid ? LG_CLIPBOARD_RESULT_ACCEPTED :
    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 = remoteRequestTakeNL(descriptor->request);
  const bool valid = finalSize <= SIZE_MAX && request && request->began &&
    sameRemoteFileRequest(request, descriptor) &&
    request->dataSize == (size_t)finalSize &&
    (request->sizeHint == KVMFR_CLIPBOARD_SIZE_UNKNOWN ||
     request->sizeHint == finalSize) &&
    (descriptor->operation != LG_CLIPBOARD_FILE_READ ||
     finalSize == descriptor->length);
  LG_UNLOCK(files.lock);
  if (!valid)
  {
    failRemoteRequest(request, EIO);
    return LG_CLIPBOARD_RESULT_FAILED;
  }
  finishRemoteRequest(request);
  return LG_CLIPBOARD_RESULT_ACCEPTED;
}

void clipboardFiles_remoteCancel(uint64_t dataset,
    uint64_t requestId, LG_ClipboardFileError reason)
{
  LG_LOCK(files.lock);
  struct RemoteRequest * request = remoteRequestTakeNL(requestId);
  bool acquisition = false;
  uint64_t failedDataset = 0;
  struct RemoteAction action = { 0 };
  for (struct RemoteDataset * item = files.remoteDatasets;
      item; item = item->next)
    if (item->dataset == dataset && item->acquisition == requestId)
    {
      item->acquiring        = false;
      item->acquired         = false;
      failedDataset          = failCurrentRemoteDatasetNL(item);
      item->ready            = false;
      item->current          = false;
      item->stale            = true;
      item->deliveryPending  = false;
      item->deliveryDeadline = 0;
      if (files.remoteCurrent == item)
        files.remoteCurrent = NULL;
      acquisition            = true;
      action                 = retireRemoteDatasetNL(item);
      break;
    }
  LG_UNLOCK(files.lock);
  if (acquisition)
    DEBUG_WARN("Remote clipboard file acquisition failed: "
        "dataset=%lu, acquisition=%lu, error=%u (%s)",
        dataset, requestId, (unsigned)reason, fileErrorName(reason));
  if (acquisition && !request)
  {
    performRemoteAction(action);
    if (failedDataset)
      clipboard_fileRemoteFailed(failedDataset);
    return;
  }
  if (!request || request->request.dataset != dataset)
  {
    failRemoteRequest(request, EIO);
    performRemoteAction(action);
    if (failedDataset)
      clipboard_fileRemoteFailed(failedDataset);
    return;
  }
  failRemoteRequest(request, errnoFromFileError(reason));
  performRemoteAction(action);
  if (failedDataset)
    clipboard_fileRemoteFailed(failedDataset);
}

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 = files.localDatasets;
  while (dataset && dataset->dataset != wireDataset)
    dataset = dataset->next;
  if (dataset && item)
  {
    item->dataset     = dataset;
    item->wireDataset = wireDataset;
    item->acquisition = acquisition;
    item->next        = files.localAcquisitions;
    files.localAcquisitions = item;
    ++dataset->references;
    error = LG_CLIPBOARD_FILE_ERROR_NONE;
  }
  else if (!item)
    error = LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
  LG_UNLOCK(files.lock);
  if (error)
    free(item);
  clipboard_fileAcquired(wireDataset, acquisition, error);
}

void clipboardFiles_localRelease(uint64_t wireDataset,
    uint64_t acquisition)
{
  LG_LOCK(files.lock);
  struct LocalAcquisition ** link = &files.localAcquisitions;
  while (*link && ((*link)->wireDataset != wireDataset ||
      (*link)->acquisition != acquisition))
    link = &(*link)->next;
  struct LocalAcquisition * item = *link;
  struct LocalDataset * retired = NULL;
  if (item)
  {
    *link = item->next;
    if (item->dataset->references)
      --item->dataset->references;
    retired = takeUnusedLocalDatasetNL(item->dataset);
  }
  LG_UNLOCK(files.lock);
  free(item);
  freeLocalDataset(retired);
}

static struct LocalDataset * acquiredLocalDatasetNL(uint64_t wireDataset)
{
  for (struct LocalDataset * dataset = files.localDatasets;
      dataset; dataset = dataset->next)
    if (dataset->dataset == wireDataset)
      return dataset;
  return NULL;
}

static bool sendJobCall(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 sendBufferJob(struct LocalJob * job,
    const uint8_t * data, size_t size)
{
  if (!sendJobCall(job, 0, size, NULL, 0, false))
    return false;
  size_t offset = 0;
  while (offset < size)
  {
    size_t chunk = size - offset;
    if (chunk > FILE_STREAM_CHUNK)
      chunk = FILE_STREAM_CHUNK;
    const bool end = chunk == size - offset;
    if (!sendJobCall(job, 1, offset, data + offset, chunk, end))
      return false;
    offset += chunk;
  }
  return sendJobCall(job, 2, size, NULL, 0, true);
}

static LG_ClipboardFileError appendLocalEntry(uint8_t ** data, size_t * size,
    size_t * capacity, const struct LocalNode * node)
{
  const size_t nameLength = strlen(node->name);
  if (!nameLength || nameLength > NAME_MAX ||
      !validUtf8((const uint8_t *)node->name, nameLength))
    return LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
  const uint64_t bytes64 = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(nameLength);
  if (bytes64 > SIZE_MAX || *size > SIZE_MAX - (size_t)bytes64)
    return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
  const size_t wanted = *size + (size_t)bytes64;
  if (!wanted || !grow((void **)data, capacity, wanted) || !*data)
    return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
  KVMFRClipboardFileEntry entry =
  {
    .node       = node->node,
    .size       = node->size,
    .createdNs  = node->createdNs,
    .modifiedNs = node->modifiedNs,
    .type       = node->type,
    .nameLength = (uint32_t)nameLength,
  };
  memcpy(*data + *size, &entry, sizeof(entry));
  memcpy(*data + *size + sizeof(entry), node->name, nameLength);
  memset(*data + *size + sizeof(entry) + nameLength, 0,
      (size_t)bytes64 - sizeof(entry) - nameLength);
  *size += (size_t)bytes64;
  return LG_CLIPBOARD_FILE_ERROR_NONE;
}

static bool localJobCancelled(const struct LocalJob * job)
{
  return atomic_load_explicit(&job->cancelled, memory_order_acquire);
}

static LG_ClipboardFileError localList(struct LocalJob * job)
{
  uint8_t * data = NULL;
  size_t size = 0;
  size_t capacity = 0;
  struct LocalNode ** listed = NULL;
  size_t listedCount = 0;
  size_t listedCapacity = 0;
  LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_IO;
  LG_LOCK(files.lock);
  struct LocalNode * directory = job->request.node ?
    localNodeByIdNL(job->dataset, job->request.node) : NULL;
  if (job->request.node && !directory)
  {
    LG_UNLOCK(files.lock);
    return LG_CLIPBOARD_FILE_ERROR_NOT_FOUND;
  }
  if (directory &&
      directory->type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY)
  {
    LG_UNLOCK(files.lock);
    return LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY;
  }
  if (!job->request.node)
  {
    struct LocalNode ** roots = NULL;
    size_t rootCount = 0;
    size_t rootCapacity = 0;
    for (struct LocalNode * node = job->dataset->nodes;
        node; node = node->next)
      if (!node->parent)
      {
        if (rootCount == SIZE_MAX / sizeof(*roots))
        {
          LG_UNLOCK(files.lock);
          free(roots);
          return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
        }
        if (!grow((void **)&roots, &rootCapacity,
              (rootCount + 1U) * sizeof(*roots)))
        {
          LG_UNLOCK(files.lock);
          free(roots);
          return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
        }
        roots[rootCount++] = node;
      }
    LG_UNLOCK(files.lock);

    for (size_t i = 0; i < rootCount; ++i)
    {
      if (localJobCancelled(job))
      {
        free(roots);
        free(data);
        return LG_CLIPBOARD_FILE_ERROR_CANCELLED;
      }
      struct stat value;
      const int fd = openLocalNode(roots[i], &value);
      if (fd < 0)
      {
        error = fileErrorFromErrno(errno);
        free(roots);
        free(data);
        return error;
      }
      close(fd);
      error = appendLocalEntry(&data, &size, &capacity, roots[i]);
      if (error)
      {
        free(roots);
        free(data);
        return error;
      }
    }
    for (size_t i = 0; i < rootCount; ++i)
    {
      if (localJobCancelled(job))
      {
        free(roots);
        free(data);
        return LG_CLIPBOARD_FILE_ERROR_CANCELLED;
      }
      struct stat value;
      const int fd = openLocalNode(roots[i], &value);
      if (fd < 0)
      {
        error = fileErrorFromErrno(errno);
        free(roots);
        free(data);
        return error;
      }
      close(fd);
    }
    free(roots);
    const bool result = sendBufferJob(job, data, size);
    free(data);
    return result ? LG_CLIPBOARD_FILE_ERROR_NONE :
      localJobCancelled(job) ? LG_CLIPBOARD_FILE_ERROR_CANCELLED :
      LG_CLIPBOARD_FILE_ERROR_DISCONNECTED;
  }
  LG_UNLOCK(files.lock);

  if (localJobCancelled(job))
    return LG_CLIPBOARD_FILE_ERROR_CANCELLED;
  struct stat directoryBefore;
  const int directoryFd = openLocalNode(directory, &directoryBefore);
  if (directoryFd < 0)
    return fileErrorFromErrno(errno);
  DIR * dir = fdopendir(directoryFd);
  if (!dir)
  {
    error = fileErrorFromErrno(errno);
    close(directoryFd);
    return error;
  }

  for (;;)
  {
    if (localJobCancelled(job))
    {
      error = LG_CLIPBOARD_FILE_ERROR_CANCELLED;
      goto failed;
    }
    errno = 0;
    struct dirent * entry = readdir(dir);
    if (!entry)
    {
      if (errno)
      {
        error = fileErrorFromErrno(errno);
        goto failed;
      }
      break;
    }
    if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
      continue;

    struct stat before;
    if (fstatat(directoryFd, entry->d_name, &before,
          AT_SYMLINK_NOFOLLOW) < 0)
    {
      error = fileErrorFromErrno(errno);
      goto failed;
    }
    if (S_ISLNK(before.st_mode))
    {
      error = LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
      goto failed;
    }
    if (!localStatSupported(&before))
    {
      error = LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
      goto failed;
    }

    int flags = O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK;
    if (S_ISDIR(before.st_mode))
      flags |= O_DIRECTORY;
    const int childFd = openat(directoryFd, entry->d_name, flags);
    if (childFd < 0)
    {
      error = fileErrorFromErrno(errno);
      goto failed;
    }
    struct stat value;
    if (fstat(childFd, &value) < 0)
    {
      error = fileErrorFromErrno(errno);
      close(childFd);
      goto failed;
    }
    const bool stable = localStatEqual(&before, &value);
    close(childFd);
    if (!stable)
    {
      error = LG_CLIPBOARD_FILE_ERROR_STALE;
      goto failed;
    }

    const size_t parentLength = strlen(directory->path);
    const size_t nameLength = strlen(entry->d_name);
    if (parentLength > SIZE_MAX - nameLength - 2U)
    {
      error = LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
      goto failed;
    }
    const size_t pathLength = parentLength + (parentLength ? 1U : 0U) +
      nameLength + 1U;
    char * childPath = malloc(pathLength);
    if (!childPath)
    {
      error = LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
      goto failed;
    }
    if (parentLength)
      snprintf(childPath, pathLength, "%s/%s",
          directory->path, entry->d_name);
    else
      memcpy(childPath, entry->d_name, nameLength + 1U);

    LG_LOCK(files.lock);
    struct LocalNode * node = localChildNL(
        job->dataset, directory->node, entry->d_name);
    if (!node)
      node = addLocalNode(job->dataset, directory->node,
          directory->root, childPath, entry->d_name, &value);
    else
      free(childPath);
    if (!node)
      error = LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
    else if (!localNodeMatches(node, &value))
      error = LG_CLIPBOARD_FILE_ERROR_STALE;
    else
      error = appendLocalEntry(&data, &size, &capacity, node);
    LG_UNLOCK(files.lock);
    if (error)
      goto failed;
    if (listedCount == SIZE_MAX / sizeof(*listed) ||
        !grow((void **)&listed, &listedCapacity,
          (listedCount + 1U) * sizeof(*listed)))
    {
      error = LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
      goto failed;
    }
    listed[listedCount++] = node;
  }

  for (size_t i = 0; i < listedCount; ++i)
  {
    if (localJobCancelled(job))
    {
      error = LG_CLIPBOARD_FILE_ERROR_CANCELLED;
      goto failed;
    }
    struct stat value;
    const int fd = openLocalNode(listed[i], &value);
    if (fd < 0)
    {
      error = fileErrorFromErrno(errno);
      goto failed;
    }
    close(fd);
  }
  struct stat directoryAfter;
  if (localJobCancelled(job))
  {
    error = LG_CLIPBOARD_FILE_ERROR_CANCELLED;
    goto failed;
  }
  if (fstat(directoryFd, &directoryAfter) < 0)
  {
    error = fileErrorFromErrno(errno);
    goto failed;
  }
  if (!localNodeMatches(directory, &directoryAfter))
  {
    error = LG_CLIPBOARD_FILE_ERROR_STALE;
    goto failed;
  }
  if (closedir(dir) < 0)
  {
    const int closeError = errno;
    free(listed);
    free(data);
    return fileErrorFromErrno(closeError);
  }
  free(listed);
  const bool result = sendBufferJob(job, data, size);
  free(data);
  return result ? LG_CLIPBOARD_FILE_ERROR_NONE :
    localJobCancelled(job) ? LG_CLIPBOARD_FILE_ERROR_CANCELLED :
    LG_CLIPBOARD_FILE_ERROR_DISCONNECTED;

failed:
  closedir(dir);
  free(listed);
  free(data);
  return error;
}

static LG_ClipboardFileError localRead(struct LocalJob * job)
{
  LG_LOCK(files.lock);
  struct LocalNode * node = localNodeByIdNL(
      job->dataset, job->request.node);
  LG_UNLOCK(files.lock);
  if (!node)
    return LG_CLIPBOARD_FILE_ERROR_NOT_FOUND;
  if (node->type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR)
    return LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY;
  if (job->request.offset > node->size ||
      job->request.length > node->size - job->request.offset)
    return LG_CLIPBOARD_FILE_ERROR_INVALID;
  if (localJobCancelled(job))
    return LG_CLIPBOARD_FILE_ERROR_CANCELLED;

  struct stat before;
  const int fd = openLocalNode(node, &before);
  if (fd < 0)
    return fileErrorFromErrno(errno);
  uint8_t * buffer = malloc(job->request.length ? job->request.length : 1U);
  if (!buffer)
  {
    close(fd);
    return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
  }
  LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_NONE;
  size_t received = 0;
  while (received < job->request.length)
  {
    if (localJobCancelled(job))
    {
      error = LG_CLIPBOARD_FILE_ERROR_CANCELLED;
      goto failed;
    }
#ifdef ENABLE_TESTS
    const bool forceEof = atomic_exchange_explicit(
        &forceLocalEof, false, memory_order_acq_rel);
    const ssize_t bytes = forceEof ? 0 :
      pread(fd, buffer + received, job->request.length - received,
          (off_t)(job->request.offset + received));
#else
    const ssize_t bytes = pread(fd, buffer + received,
        job->request.length - received,
        (off_t)(job->request.offset + received));
#endif
    if (bytes < 0 && errno == EINTR)
      continue;
    if (bytes < 0)
    {
      error = fileErrorFromErrno(errno);
      goto failed;
    }
    if (!bytes)
    {
      error = LG_CLIPBOARD_FILE_ERROR_IO;
      goto failed;
    }
    received += (size_t)bytes;
  }

  struct stat after;
  if (localJobCancelled(job))
  {
    error = LG_CLIPBOARD_FILE_ERROR_CANCELLED;
    goto failed;
  }
  if (fstat(fd, &after) < 0)
  {
    error = fileErrorFromErrno(errno);
    goto failed;
  }
  if (!localNodeMatches(node, &after) ||
      !localStatEqual(&before, &after))
  {
    error = LG_CLIPBOARD_FILE_ERROR_STALE;
    goto failed;
  }
  if (received != job->request.length)
  {
    error = LG_CLIPBOARD_FILE_ERROR_IO;
    goto failed;
  }
  close(fd);
  const bool result = sendBufferJob(job, buffer, received);
  free(buffer);
  return result ? LG_CLIPBOARD_FILE_ERROR_NONE :
    localJobCancelled(job) ? LG_CLIPBOARD_FILE_ERROR_CANCELLED :
    LG_CLIPBOARD_FILE_ERROR_DISCONNECTED;

failed:
  close(fd);
  free(buffer);
  return 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;
    }
    LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_CANCELLED;
    if (!atomic_load_explicit(&job->cancelled, memory_order_acquire))
      error = job->request.operation == LG_CLIPBOARD_FILE_LIST ?
        localList(job) : localRead(job);
    if (error && error != LG_CLIPBOARD_FILE_ERROR_CANCELLED &&
        !atomic_load_explicit(&job->cancelled, memory_order_acquire))
      clipboard_fileCancel(job->request.dataset,
          job->request.request, error);
    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);
    freeLocalDataset(retired);
    lgFreeEvent(job->ready);
    free(job);
  }
}

void clipboardFiles_localRequest(
    const LG_ClipboardFileRequest * request)
{
  if (!request || !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 > FILE_REQUEST_MAX ||
        request->offset > INT64_MAX ||
        request->length > (uint64_t)INT64_MAX - request->offset)))
  {
    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_LOCK(files.lock);
  job->dataset = acquiredLocalDatasetNL(request->dataset);
  if (job->dataset && !files.stopping)
  {
    ++job->dataset->references;
    *files.jobsTail = job;
    files.jobsTail  = &job->next;
  }
  const bool queued = job->dataset && !files.stopping;
  LG_UNLOCK(files.lock);
  if (queued)
  {
    lgSignalEvent(files.jobEvent);
    return;
  }
  lgFreeEvent(job->ready);
  free(job);
  clipboard_fileCancel(request->dataset, request->request,
      LG_CLIPBOARD_FILE_ERROR_STALE);
}

void clipboardFiles_localCancel(uint64_t dataset,
    uint64_t request, LG_ClipboardFileError reason)
{
  (void)reason;
  LG_LOCK(files.lock);
  struct LocalAcquisition ** acquisition = &files.localAcquisitions;
  struct LocalDataset * retired = NULL;
  while (*acquisition && ((*acquisition)->wireDataset != dataset ||
      (*acquisition)->acquisition != request))
    acquisition = &(*acquisition)->next;
  if (*acquisition)
  {
    struct LocalAcquisition * cancelled = *acquisition;
    *acquisition = cancelled->next;
    if (cancelled->dataset->references)
      --cancelled->dataset->references;
    retired = takeUnusedLocalDatasetNL(cancelled->dataset);
    free(cancelled);
  }
  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);
    }
  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);
  LG_UNLOCK(files.lock);
  freeLocalDataset(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);
}

static void freeState(void)
{
  struct RemoteRequest * request = files.remoteRequests;
  while (request)
  {
    struct RemoteRequest * next = request->next;
    unregisterRemoteInterrupt(request);
    if (request->fuseRequest)
      fuse_reply_err(request->fuseRequest, EIO);
    freeRemoteRequest(request);
    request = next;
  }
  struct RemoteNode * remoteNode = files.remoteNodes;
  while (remoteNode)
  {
    struct RemoteNode * next = remoteNode->next;
    free(remoteNode->name);
    free(remoteNode);
    remoteNode = next;
  }
  struct RemoteDataset * remote = files.remoteDatasets;
  while (remote)
  {
    struct RemoteDataset * next = remote->next;
    if (remote->acquired)
      clipboard_fileRelease(remote->dataset, remote->acquisition);
    else if (remote->acquiring)
      clipboard_fileCancel(remote->dataset, remote->acquisition,
          LG_CLIPBOARD_FILE_ERROR_CANCELLED);
    free(remote);
    remote = next;
  }
  struct LocalAcquisition * acquisition = files.localAcquisitions;
  while (acquisition)
  {
    struct LocalAcquisition * next = acquisition->next;
    free(acquisition);
    acquisition = next;
  }
  struct LocalDataset * local = files.localDatasets;
  while (local)
  {
    struct LocalDataset * next = local->next;
    freeLocalDataset(local);
    local = next;
  }
}

bool clipboardFiles_init(void)
{
  if (filesLockInitialized)
    return false;
  memset(&files, 0, sizeof(files));
  files.fuseStopFd = -1;
  LG_LOCK_INIT(files.lock);
  filesLockInitialized = true;
  uint64_t nonce = 0;
  if (!randomBytes(&nonce, sizeof(nonce)))
  {
    DEBUG_ERROR("Failed to seed clipboard file transfer identifiers: %s",
        strerror(errno));
    goto failed;
  }
  nonce &= ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
  if (!nonce)
    nonce = 1;
  files.requestSerial = nonce;
  files.datasetSerial = (nonce ^ UINT64_C(0x4c474346494c4553)) &
    ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
  if (!files.datasetSerial)
    files.datasetSerial = 1;
  files.inodeSerial = FUSE_ROOT_ID;
  files.jobsTail    = &files.jobsHead;
  files.jobEvent    = lgCreateEvent(false, 0);
  if (!files.jobEvent)
    goto failed;
  files.fuseStopFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
  if (files.fuseStopFd < 0)
  {
    DEBUG_ERROR("Failed to create the clipboard FUSE stop event: %s",
        strerror(errno));
    goto failed;
  }
  const char * runtime = getenv("XDG_RUNTIME_DIR");
  if (!runtime || !*runtime)
  {
    DEBUG_ERROR("XDG_RUNTIME_DIR is required for clipboard file transfer");
    goto failed;
  }
  const int length = snprintf(files.mountpoint, sizeof(files.mountpoint),
      "%s/looking-glass-clipboard-XXXXXX", runtime);
  if (length < 0 || (size_t)length >= sizeof(files.mountpoint) ||
      !mkdtemp(files.mountpoint))
  {
    DEBUG_ERROR("Failed to create the clipboard FUSE mountpoint: %s",
        strerror(errno));
    goto failed;
  }
  chmod(files.mountpoint, 0700);
  char mountOptions[160];
  const int optionsLength = snprintf(mountOptions, sizeof(mountOptions),
      "ro,nodev,nosuid,noexec,default_permissions,auto_unmount,max_read=%u",
      (unsigned)FILE_REQUEST_MAX);
  if (optionsLength < 0 || (size_t)optionsLength >= sizeof(mountOptions))
  {
    DEBUG_ERROR("Failed to format clipboard FUSE mount options");
    goto failed;
  }
  struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
  if (fuse_opt_add_arg(&args, "looking-glass-client") < 0 ||
      fuse_opt_add_arg(&args, "-o") < 0 ||
      fuse_opt_add_arg(&args, mountOptions) < 0)
  {
    fuse_opt_free_args(&args);
    goto failed;
  }
  files.session = fuse_session_new(&args, &fuseOps,
      sizeof(fuseOps), NULL);
  fuse_opt_free_args(&args);
  if (!files.session || fuse_session_mount(
      files.session, files.mountpoint) < 0)
  {
    DEBUG_ERROR("Failed to mount the clipboard FUSE filesystem");
    goto failed;
  }
  if (!lgCreateThread("clipboard-fuse", fuseThread,
      files.session, &files.fuseThread))
    goto failed;
  unsigned created = 0;
  for (; created < FILE_WORKERS; ++created)
    if (!lgCreateThread("clipboard-file", localWorker,
        NULL, &files.workers[created]))
      break;
  if (created != FILE_WORKERS)
    goto failed;
  files.initialized = true;
  DEBUG_INFO("Clipboard files mounted at %s", files.mountpoint);
  return true;

failed:
  clipboardFiles_free();
  return false;
}

#ifdef ENABLE_TESTS
bool clipboardFiles_testFuseStopWake(void)
{
  const int stopFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
  if (stopFd < 0)
    return false;
  const bool result = wakeFuseThread(stopFd) &&
    waitFuseEvent(-1, stopFd, -1) == FUSE_WAIT_WAKE;
  close(stopFd);
  return result;
}

bool clipboardFiles_testUnsentRemoteOwnership(void)
{
  return finishUnsentRemoteRequest(0);
}

bool clipboardFiles_testInit(uint64_t nonce)
{
  if (filesLockInitialized)
    return false;
  nonce &= ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
  if (!nonce)
    return false;
  memset(&files, 0, sizeof(files));
  files.fuseStopFd = -1;
  LG_LOCK_INIT(files.lock);
  filesLockInitialized = true;
  files.requestSerial = nonce;
  files.datasetSerial = (nonce ^ UINT64_C(0x4c474346494c4553)) &
    ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
  if (!files.datasetSerial)
    files.datasetSerial = 1;
  files.inodeSerial   = FUSE_ROOT_ID;
  files.jobsTail      = &files.jobsHead;
  files.initialized   = true;
  atomic_store_explicit(&forceLocalEof, false, memory_order_release);
  return true;
}

size_t clipboardFiles_testRemoteDatasetCount(void)
{
  if (!filesLockInitialized)
    return 0;
  size_t count = 0;
  LG_LOCK(files.lock);
  for (struct RemoteDataset * dataset = files.remoteDatasets;
      dataset; dataset = dataset->next)
    ++count;
  LG_UNLOCK(files.lock);
  return count;
}

void clipboardFiles_testExpireRemoteDeliveries(void)
{
  if (!filesLockInitialized)
    return;
  LG_LOCK(files.lock);
  for (struct RemoteDataset * dataset = files.remoteDatasets;
      dataset; dataset = dataset->next)
    if (dataset->deliveryPending)
      dataset->deliveryDeadline = 0;
  LG_UNLOCK(files.lock);
  reapRemoteDeliveries(false);
}

bool clipboardFiles_testBeginRemoteLookup(uint64_t presentation)
{
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset =
    remoteDatasetByAcquisitionNL(presentation);
  const bool result = dataset && dataset->ready &&
    pinRemoteOperationNL(dataset, true);
  LG_UNLOCK(files.lock);
  return result;
}

void clipboardFiles_testEndRemoteLookup(uint64_t presentation)
{
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset =
    remoteDatasetByAcquisitionNL(presentation);
  if (dataset && dataset->operationPins)
    --dataset->operationPins;
  const struct RemoteAction action = retireRemoteDatasetNL(dataset);
  LG_UNLOCK(files.lock);
  performRemoteAction(action);
}

bool clipboardFiles_testRemoteRead(uint64_t presentation,
    uint64_t nodeId, uint64_t offset, uint32_t length)
{
  LG_LOCK(files.lock);
  struct RemoteDataset * dataset =
    remoteDatasetByAcquisitionNL(presentation);
  struct RemoteNode * node = dataset ?
    remoteNodeByWireNL(dataset, nodeId) : NULL;
  const bool pinned = node && length &&
    pinRemoteOperationNL(dataset, false);
  LG_UNLOCK(files.lock);
  if (!pinned)
    return false;
  const bool result = requestRemote(dataset, node,
      LG_CLIPBOARD_FILE_READ, offset, length, REMOTE_REPLY_READ,
      NULL, 0, 0, NULL);
  releaseRemoteOperation(dataset);
  return result;
}

void clipboardFiles_testForceLocalEof(void)
{
  atomic_store_explicit(&forceLocalEof, true, memory_order_release);
}

bool clipboardFiles_testLocalRequest(
    const LG_ClipboardFileRequest * request)
{
  if (!request ||
      (request->operation != LG_CLIPBOARD_FILE_LIST &&
       request->operation != LG_CLIPBOARD_FILE_READ))
    return false;

  struct LocalJob job = { .request = *request };
  atomic_init(&job.cancelled, false);
  LG_LOCK(files.lock);
  job.dataset = acquiredLocalDatasetNL(request->dataset);
  if (job.dataset)
    ++job.dataset->references;
  LG_UNLOCK(files.lock);
  if (!job.dataset)
    return false;

  const LG_ClipboardFileError error =
    request->operation == LG_CLIPBOARD_FILE_LIST ?
    localList(&job) : localRead(&job);
  if (error && error != LG_CLIPBOARD_FILE_ERROR_CANCELLED)
    clipboard_fileCancel(request->dataset, request->request, error);
  LG_LOCK(files.lock);
  if (job.dataset->references)
    --job.dataset->references;
  LG_UNLOCK(files.lock);
  return error == LG_CLIPBOARD_FILE_ERROR_NONE;
}
#endif

void clipboardFiles_free(void)
{
  if (!filesLockInitialized)
    return;
  LG_LOCK(files.lock);
  files.stopping = true;
  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);
    if (job->ready)
      lgSignalEvent(job->ready);
  }
  LG_UNLOCK(files.lock);
  if (files.jobEvent)
    lgSignalEvent(files.jobEvent);
  for (unsigned i = 0; i < FILE_WORKERS; ++i)
    if (files.workers[i])
    {
      lgSignalEvent(files.jobEvent);
      lgJoinThread(files.workers[i], NULL);
      files.workers[i] = NULL;
    }
  if (files.session)
    fuse_session_exit(files.session);
  if (files.fuseThread)
  {
    if (!wakeFuseThread(files.fuseStopFd))
      DEBUG_WARN("Failed to wake the clipboard FUSE thread: %s",
          strerror(errno));
    lgJoinThread(files.fuseThread, NULL);
    files.fuseThread = NULL;
  }
  if (files.fuseStopFd >= 0)
  {
    close(files.fuseStopFd);
    files.fuseStopFd = -1;
  }
  /* Pending requests belong to the stopped but still-live session. */
  freeState();
  if (files.session)
  {
    fuse_session_unmount(files.session);
    fuse_session_destroy(files.session);
    files.session = NULL;
  }
  if (files.jobEvent)
  {
    lgFreeEvent(files.jobEvent);
    files.jobEvent = NULL;
  }
  if (files.mountpoint[0] && rmdir(files.mountpoint) < 0 && errno != ENOENT)
    DEBUG_WARN("Failed to remove clipboard mountpoint: %s", strerror(errno));
  files.mountpoint[0] = 0;
  files.initialized = false;
  LG_LOCK_FREE(files.lock);
  filesLockInitialized = false;
}
