/****************************************************************************
 * libs/libc/userfs/lib_userfs.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/socket.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <arpa/inet.h>
#include <netinet/in.h>

#include <nuttx/fs/userfs.h>
#include <nuttx/mutex.h>

#include "libc.h"

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct userfs_info_s
{
  FAR const struct userfs_operations_s *userops;
  FAR void *volinfo;          /* Data that accompanies the user callbacks */
  struct sockaddr_in client;  /* Client to send response back to */
  int16_t sockfd;             /* Server socket */
  uint16_t iolen;             /* Size of I/O buffer */
  uint16_t mxwrite;           /* The max size of a write data */
  uint8_t iobuffer[1];        /* I/O buffer.  Actual size is iolen. */
};

#define SIZEOF_USERFS_INFO_S(n) (sizeof(struct userfs_info_s) + (n) - 1)

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* REVISIT:  This is insufficient in the KERNEL build.  In that build mode,
 * there will be multiple instances of these variables and the logic will
 * not generate unique instance numbers.
 */

static mutex_t g_userfs_lock = NXMUTEX_INITIALIZER;
static uint8_t g_userfs_next_instance;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: userfs_server_portno
 ****************************************************************************/

static inline uint16_t userfs_server_portno(void)
{
  int ret;

  ret = nxmutex_lock(&g_userfs_lock);
  if (ret >= 0)
    {
      /* Get the next instance number.
       *
       * REVISIT: Here we really should verify that other UserFs
       * exists with the same instance number.  That could
       * happen if g_userfs_next_instance were to wrap around.
       */

      ret = USERFS_SERVER_PORTBASE | g_userfs_next_instance++;
      nxmutex_unlock(&g_userfs_lock);
    }

  return ret;
}

/****************************************************************************
 * Name: userfs_*_dispatch
 ****************************************************************************/

static inline int userfs_open_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_open_request_s *req, size_t reqlen)
{
  struct userfs_open_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;
  int ret;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_OPEN_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_OPEN_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request.
   *
   * REVISIT: In the kernel build openinfo will be valid only in the
   * context of this process.
   */

  DEBUGASSERT(info->userops != NULL && info->userops->open != NULL);
  resp.ret  = info->userops->open(info->volinfo, req->relpath, req->oflags,
                                  req->mode, &resp.openinfo);

  /* Send the response */

  resp.resp = USERFS_RESP_OPEN;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_open_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ret = -get_errno();
      ferr("ERROR: Send open response failed: %d\n", ret);
      return ret;
    }

  /* REVISIT: Partial sends are not supported */

  DEBUGASSERT(nsent == sizeof(struct userfs_open_response_s));
  return OK;
}

static inline int userfs_close_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_close_request_s *req, size_t reqlen)
{
  struct userfs_close_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_close_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->close != NULL);
  resp.ret  = info->userops->close(info->volinfo, req->openinfo);

  /* Send the response */

  resp.resp = USERFS_RESP_CLOSE;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_close_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_read_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_read_request_s *req, size_t reqlen)
{
  FAR struct userfs_read_response_s *resp;
  size_t readlen;
  size_t resplen;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_read_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  readlen = req->readlen;
  if (readlen > info->mxwrite)
    {
      readlen = info->mxwrite;
    }

  resp = (FAR struct userfs_read_response_s *)info->iobuffer;

  DEBUGASSERT(info->userops != NULL && info->userops->read != NULL);
  resp->nread = info->userops->read(info->volinfo, req->openinfo,
                                    resp->rddata, readlen);

  /* Send the response */

  resp->resp  = USERFS_RESP_READ;
  resplen     = SIZEOF_USERFS_READ_RESPONSE_S(resp->nread);
  nsent       = sendto(info->sockfd, resp, resplen, 0,
                       (FAR struct sockaddr *)&info->client,
                       sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_write_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_write_request_s *req, size_t reqlen)
{
  struct userfs_write_response_s resp;
  size_t writelen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_WRITE_REQUEST_S(0))
    {
      return -EINVAL;
    }

  writelen = req->writelen;
  if (writelen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_WRITE_REQUEST_S(writelen);
  if (expected != reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->write != NULL);
  resp.nwritten = info->userops->write(info->volinfo, req->openinfo,
                                       req->wrdata, writelen);

  /* Send the response */

  resp.resp     = USERFS_RESP_WRITE;
  nsent         = sendto(info->sockfd, &resp,
                         sizeof(struct userfs_write_response_s),
                         0, (FAR struct sockaddr *)&info->client,
                         sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_seek_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_seek_request_s *req, size_t reqlen)
{
  struct userfs_seek_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_seek_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->seek != NULL);
  resp.ret  = info->userops->seek(info->volinfo, req->openinfo, req->offset,
                                  req->whence);

  /* Send the response */

  resp.resp = USERFS_RESP_SEEK;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_seek_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_ioctl_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_ioctl_request_s *req, size_t reqlen)
{
  struct userfs_ioctl_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_ioctl_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->ioctl != NULL);
  resp.ret  = info->userops->ioctl(info->volinfo, req->openinfo, req->cmd,
                                   req->arg);

  /* Send the response */

  resp.resp = USERFS_RESP_IOCTL;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_ioctl_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_sync_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_sync_request_s *req, size_t reqlen)
{
  struct userfs_sync_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_sync_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->sync != NULL);
  resp.ret  = info->userops->sync(info->volinfo, req->openinfo);

  /* Send the response */

  resp.resp = USERFS_RESP_SYNC;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_sync_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_dup_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_dup_request_s *req, size_t reqlen)
{
  struct userfs_dup_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_dup_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->dup != NULL);
  resp.ret  = info->userops->dup(info->volinfo,
                                 req->openinfo, &resp.openinfo);

  /* Send the response */

  resp.resp = USERFS_RESP_DUP;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_dup_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_fstat_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_fstat_request_s *req, size_t reqlen)
{
  struct userfs_fstat_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_fstat_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->fstat != NULL);
  resp.ret  = info->userops->fstat(info->volinfo, req->openinfo, &resp.buf);

  /* Send the response */

  resp.resp = USERFS_RESP_FSTAT;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_fstat_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_truncate_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_truncate_request_s *req, size_t reqlen)
{
  struct userfs_truncate_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_truncate_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->truncate != NULL);
  resp.ret  = info->userops->truncate(info->volinfo,
                                      req->openinfo, req->length);

  /* Send the response */

  resp.resp = USERFS_RESP_FSTAT;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_truncate_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_opendir_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_opendir_request_s *req, size_t reqlen)
{
  struct userfs_opendir_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_OPENDIR_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_OPENDIR_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->opendir != NULL);
  resp.ret  = info->userops->opendir(info->volinfo, req->relpath, &resp.dir);

  /* Send the response */

  resp.resp = USERFS_RESP_OPENDIR;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_opendir_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_closedir_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_closedir_request_s *req, size_t reqlen)
{
  struct userfs_closedir_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_closedir_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->closedir != NULL);
  resp.ret  = info->userops->closedir(info->volinfo, req->dir);

  /* Send the response */

  resp.resp = USERFS_RESP_CLOSEDIR;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_closedir_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_readdir_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_readdir_request_s *req, size_t reqlen)
{
  struct userfs_readdir_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_readdir_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->readdir != NULL);
  resp.ret  = info->userops->readdir(info->volinfo, req->dir, &resp.entry);

  /* Send the response */

  resp.resp = USERFS_RESP_READDIR;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_readdir_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_rewinddir_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_rewinddir_request_s *req, size_t reqlen)
{
  struct userfs_rewinddir_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_rewinddir_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->rewinddir != NULL);
  resp.ret  = info->userops->rewinddir(info->volinfo, req->dir);

  /* Send the response */

  resp.resp = USERFS_RESP_REWINDDIR;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_rewinddir_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_statfs_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_statfs_request_s *req, size_t reqlen)
{
  struct userfs_statfs_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_statfs_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->statfs != NULL);
  resp.ret  = info->userops->statfs(info->volinfo, &resp.buf);

  /* Send the response */

  resp.resp = USERFS_RESP_STATFS;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_statfs_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_unlink_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_unlink_request_s *req, size_t reqlen)
{
  struct userfs_unlink_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_UNLINK_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_UNLINK_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->unlink != NULL);
  resp.ret  = info->userops->unlink(info->volinfo, req->relpath);

  /* Send the response */

  resp.resp = USERFS_RESP_UNLINK;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_unlink_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_mkdir_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_mkdir_request_s *req, size_t reqlen)
{
  struct userfs_mkdir_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_MKDIR_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_MKDIR_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->mkdir != NULL);
  resp.ret  = info->userops->mkdir(info->volinfo, req->relpath, req->mode);

  /* Send the response */

  resp.resp = USERFS_RESP_MKDIR;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_mkdir_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_rmdir_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_rmdir_request_s *req, size_t reqlen)
{
  struct userfs_rmdir_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_MKDIR_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_MKDIR_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->rmdir != NULL);
  resp.ret  = info->userops->rmdir(info->volinfo, req->relpath);

  /* Send the response */

  resp.resp = USERFS_RESP_RMDIR;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_rmdir_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_rename_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_rename_request_s *req, size_t reqlen)
{
  struct userfs_rename_response_s resp;
  FAR char *newrelpath;
  unsigned int newoffset;
  int oldpathlen;
  int newpathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_RENAME_REQUEST_S(0, 0))
    {
      return -EINVAL;
    }

  oldpathlen = strlen(req->oldrelpath);
  newoffset  = req->newoffset;

  if (oldpathlen >= newoffset)
    {
      return -EINVAL;
    }

  newrelpath = &req->oldrelpath[newoffset];
  newpathlen = strlen(newrelpath);

  if ((newpathlen + newoffset) > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_RENAME_REQUEST_S(newoffset, newpathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->rename != NULL);
  resp.ret  = info->userops->rename(info->volinfo, req->oldrelpath,
                                    newrelpath);

  /* Send the response */

  resp.resp = USERFS_RESP_RENAME;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_rename_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_stat_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_stat_request_s *req, size_t reqlen)
{
  struct userfs_stat_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_STAT_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_STAT_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->stat != NULL);
  resp.ret  = info->userops->stat(info->volinfo, req->relpath, &resp.buf);

  /* Send the response */

  resp.resp = USERFS_RESP_STAT;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_stat_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_destroy_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_destroy_request_s *req, size_t reqlen)
{
  struct userfs_destroy_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_destroy_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->destroy != NULL);
  resp.ret  = info->userops->destroy(info->volinfo);

  /* Send the response */

  resp.resp = USERFS_RESP_DESTROY;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_destroy_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      int ret = -get_errno();
      ferr("ERROR: sendto failed: %d\n", ret);
      return ret;
    }

  /* Speical case of resp.ret indicates an error, the destruction was
   * refused.  So we need to return success in this case so that we
   * continue processing requests.
   */

  return resp.ret < 0 ? OK : -ENOTCONN;
}

static inline int userfs_fchstat_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_fchstat_request_s *req, size_t reqlen)
{
  struct userfs_fchstat_response_s resp;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen != sizeof(struct userfs_fchstat_request_s))
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->fchstat != NULL);
  resp.ret  = info->userops->fchstat(info->volinfo, req->openinfo,
                                     &req->buf, req->flags);

  /* Send the response */

  resp.resp = USERFS_RESP_FCHSTAT;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_fchstat_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

static inline int userfs_chstat_dispatch(FAR struct userfs_info_s *info,
                   FAR struct userfs_chstat_request_s *req, size_t reqlen)
{
  struct userfs_chstat_response_s resp;
  int pathlen;
  size_t expected;
  ssize_t nsent;

  /* Verify the request size */

  if (reqlen < SIZEOF_USERFS_CHSTAT_REQUEST_S(0))
    {
      return -EINVAL;
    }

  pathlen = strlen(req->relpath);
  if (pathlen > info->mxwrite)
    {
      return -EINVAL;
    }

  expected = SIZEOF_USERFS_CHSTAT_REQUEST_S(pathlen);
  if (expected >= reqlen)
    {
      return -EINVAL;
    }

  /* Dispatch the request */

  DEBUGASSERT(info->userops != NULL && info->userops->chstat != NULL);
  resp.ret  = info->userops->chstat(info->volinfo, req->relpath,
                                    &req->buf, req->flags);

  /* Send the response */

  resp.resp = USERFS_RESP_CHSTAT;
  nsent     = sendto(info->sockfd, &resp,
                     sizeof(struct userfs_chstat_response_s),
                     0, (FAR struct sockaddr *)&info->client,
                     sizeof(struct sockaddr_in));
  return nsent < 0 ? nsent : OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: userfs_run
 *
 * Description:
 *   Start the UserFS server on the current thread.  This function will mount
 *   the UserFS file system and will not return until that file system has
 *   been unmounted.
 *
 *   userfs_run() implements the UserFS server. It performs there operations:
 *
 *   1. It configures and creates the UserFS file system and
 *   2. Mounts the user file system at the provide mount point path.
 *   2. Receives file system requests on the LocalHost socket with
 *      server port 0x83nn where nn is the same as above,
 *   3. Received file system requests are marshaled and dispatch to the
 *      user file system via callbacks to the operations provided by
 *      "userops", and
 *   3. Returns file system responses generated by the callbacks to the
 *      LocalHost client socket.
 *
 *   NOTE:  This is a user function that is implemented as part of the
 *   NuttX C library and is intended to be called by application logic.
 *
 * Input Parameters:
 *   mountpt  - Mountpoint path
 *   userops  - The caller operations that implement the file system
 *              interface.
 *   volinfo  - Private volume data that will be provided in all struct
 *              userfs_operations_s methods.
 *   mxwrite  - The max size of a write data
 *
 *  Returned Value:
 *    This function does not return unless the file system is unmounted (OK)
 *    or unless an error is encountered.  In the event of an error, the
 *    returned value is a negated errno value indicating the nature of the
 *    error.
 *
 ****************************************************************************/

int userfs_run(FAR const char *mountpt,
               FAR const struct userfs_operations_s *userops,
               FAR void *volinfo, size_t mxwrite)
{
  FAR struct userfs_info_s *info;
  FAR struct userfs_config_s config;
  struct sockaddr_in server;
  unsigned int iolen;
  socklen_t addrlen;
  ssize_t nread;
  int ret;

  DEBUGASSERT(mountpt != NULL && userops != NULL && mxwrite <= UINT16_MAX);
  DEBUGASSERT(mxwrite > 0 && mxwrite <= (UINT16_MAX - USERFS_REQ_MAXSIZE));

  /* Allocate a state structure with an I/O buffer to receive UserFS
   * requests
   */

  iolen = USERFS_REQ_MAXSIZE + mxwrite;
  info  = lib_zalloc(SIZEOF_USERFS_INFO_S(iolen));
  if (info == NULL)
    {
      ferr("ERROR: Failed to allocate state structure\n");
      return -ENOMEM;
    }

  /* Initialize the state structure */

  info->userops   = userops;
  info->volinfo   = volinfo;
  info->iolen     = iolen;
  info->mxwrite   = mxwrite;

  /* Create the UserFS configuration that will be provided as optional
   * data when the UserFS is mounted.
   */

  config.mxwrite  = mxwrite;
  config.portno   = userfs_server_portno();

  /* Mounts the user file system at the provided mount point path. */

  ret = mount(NULL, mountpt, "userfs", 0, (FAR const void *)&config);
  if (ret < 0)
    {
      ret = -get_errno();
      ferr("ERROR: mount() failed: %d\n", ret);
      goto errout_with_info;
    }

  /* Create a new LocalHost UDP server socket */

  info->sockfd = socket(PF_INET, SOCK_DGRAM, 0);
  if (info->sockfd < 0)
    {
      ret = -get_errno();
      ferr("ERROR: socket() failed: %d\n", ret);
      goto errout_with_info;
    }

  /* Bind the socket to a server port number */

  server.sin_family      = AF_INET;
  server.sin_port        = HTONS(config.portno);
  server.sin_addr.s_addr = HTONL(INADDR_LOOPBACK);

  ret = bind(info->sockfd, (struct sockaddr *)&server,
             sizeof(struct sockaddr_in));
  if (ret < 0)
    {
      ret = -get_errno();
      ferr("ERROR: bind() failed: %d\n", ret);
      goto errout_with_sockfd;
    }

  /* Receive file system requests on the POSIX message queue as long
   * as the mount persists.
   */

  do
    {
      /* Receive the next file system request */

      finfo("Receiving up %u bytes\n", info->iolen);
      addrlen = sizeof(struct sockaddr_in);
      nread   = recvfrom(info->sockfd, info->iobuffer, info->iolen, 0,
                         (FAR struct sockaddr *)&info->client,
                         &addrlen);
      if (nread < 0)
        {
          ret = -get_errno();
          ferr("ERROR: recvfrom failed: %d\n", ret);
          goto errout_with_sockfd;
        }

      DEBUGASSERT(addrlen == sizeof(struct sockaddr_in));

      /* Process the request according to its request ID */

      DEBUGASSERT(nread >= sizeof(uint8_t));
      switch (*info->iobuffer)
        {
          case USERFS_REQ_OPEN:
            ret = userfs_open_dispatch(info,
                   (FAR struct userfs_open_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_CLOSE:
            ret = userfs_close_dispatch(info,
                   (FAR struct userfs_close_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_READ:
            ret = userfs_read_dispatch(info,
                   (FAR struct userfs_read_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_WRITE:
            ret = userfs_write_dispatch(info,
                   (FAR struct userfs_write_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_SEEK:
            ret = userfs_seek_dispatch(info,
                   (FAR struct userfs_seek_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_IOCTL:
            ret = userfs_ioctl_dispatch(info,
                   (FAR struct userfs_ioctl_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_SYNC:
            ret = userfs_sync_dispatch(info,
                   (FAR struct userfs_sync_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_DUP:
            ret = userfs_dup_dispatch(info,
                   (FAR struct userfs_dup_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_FSTAT:
            ret = userfs_fstat_dispatch(info,
                   (FAR struct userfs_fstat_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_TRUNCATE:
            ret = userfs_truncate_dispatch(info,
                   (FAR struct userfs_truncate_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_OPENDIR:
            ret = userfs_opendir_dispatch(info,
                   (FAR struct userfs_opendir_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_CLOSEDIR:
            ret = userfs_closedir_dispatch(info,
                   (FAR struct userfs_closedir_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_READDIR:
            ret = userfs_readdir_dispatch(info,
                   (FAR struct userfs_readdir_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_REWINDDIR:
            ret = userfs_rewinddir_dispatch(info,
                   (FAR struct userfs_rewinddir_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_STATFS:
            ret = userfs_statfs_dispatch(info,
                   (FAR struct userfs_statfs_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_UNLINK:
            ret = userfs_unlink_dispatch(info,
                   (FAR struct userfs_unlink_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_MKDIR:
            ret = userfs_mkdir_dispatch(info,
                   (FAR struct userfs_mkdir_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_RMDIR:
            ret = userfs_rmdir_dispatch(info,
                   (FAR struct userfs_rmdir_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_RENAME:
            ret = userfs_rename_dispatch(info,
                   (FAR struct userfs_rename_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_STAT:
            ret = userfs_stat_dispatch(info,
                   (FAR struct userfs_stat_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_DESTROY:
            ret = userfs_destroy_dispatch(info,
                   (FAR struct userfs_destroy_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_FCHSTAT:
            ret = userfs_fchstat_dispatch(info,
                   (FAR struct userfs_fchstat_request_s *)info->iobuffer,
                   nread);
            break;

          case USERFS_REQ_CHSTAT:
            ret = userfs_chstat_dispatch(info,
                   (FAR struct userfs_chstat_request_s *)info->iobuffer,
                   nread);
            break;

          default:
            ferr("ERROR: Unrecognized request received: %u\n",
                 *info->iobuffer);
            ret = -EINVAL;
            break;
        }
    }
  while (ret == OK);

  /* Close the LocalHost socket */

errout_with_sockfd:
  close(info->sockfd);

  /* Free the IO Buffer */

errout_with_info:
  lib_free(info);
  return ret;
}
