/****************************************************************************
 * fs/userfs/fs_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/types.h>
#include <sys/statfs.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include <stdint.h>
#include <stdbool.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/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/userfs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/net/net.h>
#include <nuttx/mutex.h>

#include "fs_heap.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define IOBUFFER_SIZE(p) (USERFS_REQ_MAXSIZE + (p)->mxwrite)

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

struct userfs_dir_s
{
  struct fs_dirent_s base;
  FAR void *dir;
};

/* This structure holds the internal state of the UserFS proxy */

struct userfs_state_s
{
  /* Fields copied from struct userfs_config_s */

  size_t mxwrite;            /* The max size of a write data */

  /* Internal state */

  struct socket psock;       /* Client socket instance */
  struct sockaddr_in server; /* Server address */
  mutex_t lock;              /* Exclusive access for request-response sequence */

  /* I/O Buffer (actual size depends on USERFS_REQ_MAXSIZE and the configured
   * mxwrite).
   */

  uint8_t iobuffer[1];
};

#define SIZEOF_USERFS_STATE_S(n) (sizeof(struct userfs_state_s) + (n) - 1)

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int     userfs_open(FAR struct file *filep, const char *relpath,
                 int oflags, mode_t mode);
static int     userfs_close(FAR struct file *filep);
static ssize_t userfs_read(FAR struct file *filep, char *buffer,
                 size_t buflen);
static ssize_t userfs_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);
static ssize_t userfs_write(FAR struct file *filep, FAR const char *buffer,
                 size_t buflen);
static off_t   userfs_seek(FAR struct file *filep, off_t offset, int whence);
static int     userfs_ioctl(FAR struct file *filep, int cmd,
                 unsigned long arg);

static int     userfs_sync(FAR struct file *filep);
static int     userfs_dup(FAR const struct file *oldp,
                          FAR struct file *newp);
static int     userfs_fstat(FAR const struct file *filep,
                 FAR struct stat *buf);
static int     userfs_truncate(FAR struct file *filep, off_t length);

static int     userfs_opendir(FAR struct inode *mountpt,
                 FAR const char *relpath, FAR struct fs_dirent_s **dir);
static int     userfs_closedir(FAR struct inode *mountpt,
                 FAR struct fs_dirent_s *dir);
static int     userfs_readdir(FAR struct inode *mountpt,
                 FAR struct fs_dirent_s *dir,
                 FAR struct dirent *entry);
static int     userfs_rewinddir(FAR struct inode *mountpt,
                 FAR struct fs_dirent_s *dir);

static int     userfs_bind(FAR struct inode *blkdriver, FAR const void *data,
                 FAR void **handle);
static int     userfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
                 unsigned int flags);
static int     userfs_statfs(FAR struct inode *mountpt,
                 FAR struct statfs *buf);

static int     userfs_unlink(FAR struct inode *mountpt,
                 FAR const char *relpath);
static int     userfs_mkdir(FAR struct inode *mountpt,
                 FAR const char *relpath, mode_t mode);
static int     userfs_rmdir(FAR struct inode *mountpt,
                 FAR const char *relpath);
static int     userfs_rename(FAR struct inode *mountpt,
                 FAR const char *oldrelpath, FAR const char *newrelpath);
static int     userfs_stat(FAR struct inode *mountpt,
                 FAR const char *relpath, FAR struct stat *buf);
static int     userfs_fchstat(FAR const struct file *filep,
                 FAR const struct stat *buf, int flags);
static int     userfs_chstat(FAR struct inode *mountpt,
                 FAR const char *relpath,
                 FAR const struct stat *buf, int flags);

/****************************************************************************
 * Public Data
 ****************************************************************************/

/* See fs_mount.c -- this structure is explicitly extern'ed there.
 * We use the old-fashioned kind of initializers so that this will compile
 * with any compiler.
 */

const struct mountpt_operations g_userfs_operations =
{
  userfs_open,       /* open */
  userfs_close,      /* close */
  userfs_read,       /* read */
  userfs_write,      /* write */
  userfs_seek,       /* seek */
  userfs_ioctl,      /* ioctl */
  NULL,              /* mmap */
  userfs_truncate,   /* truncate */
  NULL,              /* poll */

  userfs_sync,       /* sync */
  userfs_dup,        /* dup */
  userfs_fstat,      /* fstat */
  userfs_fchstat,    /* fchstat */

  userfs_opendir,    /* opendir */
  userfs_closedir,   /* closedir */
  userfs_readdir,    /* readdir */
  userfs_rewinddir,  /* rewinddir */

  userfs_bind,       /* bind */
  userfs_unbind,     /* unbind */
  userfs_statfs,     /* statfs */

  userfs_unlink,     /* unlink */
  userfs_mkdir,      /* mkdir */
  userfs_rmdir,      /* rmdir */
  userfs_rename,     /* rename */
  userfs_stat,       /* stat */
  userfs_chstat      /* chstat */
};

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

/****************************************************************************
 * Name: userfs_open
 ****************************************************************************/

static int userfs_open(FAR struct file *filep, FAR const char *relpath,
                      int oflags, mode_t mode)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_open_request_s *req;
  FAR struct userfs_open_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  finfo("Open '%s'\n", relpath);

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req         = (FAR struct userfs_open_request_s *)priv->iobuffer;
  req->req    = USERFS_REQ_OPEN;
  req->oflags = oflags;
  req->mode   = mode;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_OPEN_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_open_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  /* Save the returned openinfo as the filep private data. */

  resp = (FAR struct userfs_open_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_OPEN)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  filep->f_priv = resp->openinfo;
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_close
 ****************************************************************************/

static int userfs_close(FAR struct file *filep)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_close_request_s *req;
  FAR struct userfs_close_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_close_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_CLOSE;
  req->openinfo = filep->f_priv;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_close_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_close_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_close_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_CLOSE)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  if (resp->ret >= 0)
    {
      filep->f_priv = NULL;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_read
 ****************************************************************************/

static ssize_t userfs_read(FAR struct file *filep, char *buffer,
                           size_t buflen)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_read_request_s *req;
  FAR struct userfs_read_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int respsize;
  int ret;

  finfo("Read %zu bytes from offset %jd\n", buflen, (intmax_t)filep->f_pos);

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_read_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_READ;
  req->openinfo = filep->f_priv;
  req->readlen  = buflen;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_read_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd < SIZEOF_USERFS_READ_RESPONSE_S(0))
    {
      ferr("ERROR: Response too small: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_read_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_READ)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  if (resp->nread > buflen)
    {
      ferr("ERROR: Response size too large: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  respsize = SIZEOF_USERFS_READ_RESPONSE_S(resp->nread);
  if (respsize != nrecvd)
    {
      ferr("ERROR: Incorrect response size: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  /* Copy the received data to the user buffer */

  memcpy(buffer, resp->rddata, resp->nread);
  return resp->nread;
}

/****************************************************************************
 * Name: userfs_write
 ****************************************************************************/

static ssize_t userfs_write(FAR struct file *filep, FAR const char *buffer,
                            size_t buflen)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_write_request_s *req;
  FAR struct userfs_write_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  finfo("Write %zu bytes to offset %jd\n", buflen, (intmax_t)filep->f_pos);

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Perform multiple writes if the write length exceeds the configured
   * maximum (mxwrite).
   */

  if (buflen > priv->mxwrite)
    {
      return -E2BIG; /* No implemented yet */
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_write_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_WRITE;
  req->openinfo = filep->f_priv;
  req->writelen = buflen;
  memcpy(req->wrdata, buffer, buflen);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_WRITE_REQUEST_S(buflen), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_write_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_write_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_WRITE)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->nwritten;
}

/****************************************************************************
 * Name: userfs_seek
 ****************************************************************************/

static off_t userfs_seek(FAR struct file *filep, off_t offset, int whence)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_seek_request_s *req;
  FAR struct userfs_seek_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  finfo("Offset %lu bytes to whence=%d\n", (unsigned long)offset, whence);

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_seek_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_SEEK;
  req->openinfo = filep->f_priv;
  req->offset   = offset;
  req->whence   = whence;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_seek_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_seek_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_seek_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_SEEK)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_ioctl
 ****************************************************************************/

static int userfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_ioctl_request_s *req;
  FAR struct userfs_ioctl_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  finfo("cmd: %d arg: %08lx\n", cmd, arg);

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_ioctl_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_IOCTL;
  req->openinfo = filep->f_priv;
  req->cmd      = cmd;
  req->arg      = arg;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_ioctl_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_ioctl_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_ioctl_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_IOCTL)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_sync
 ****************************************************************************/

static int userfs_sync(FAR struct file *filep)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_sync_request_s *req;
  FAR struct userfs_sync_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_sync_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_SYNC;
  req->openinfo = filep->f_priv;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_sync_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_sync_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_sync_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_SYNC)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_dup
 *
 * Description:
 *   Duplicate open file data in the new file structure.
 *
 ****************************************************************************/

static int userfs_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_dup_request_s *req;
  FAR struct userfs_dup_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  finfo("Dup %p->%p\n", oldp, newp);

  DEBUGASSERT(oldp != NULL &&
              oldp->f_inode != NULL &&
              oldp->f_inode->i_private != NULL);
  priv = oldp->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_dup_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_DUP;
  req->openinfo = oldp->f_priv;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_dup_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_dup_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_dup_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_DUP)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  newp->f_priv = resp->openinfo;
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_fstat
 *
 * Description:
 *   Obtain information about an open file associated with the file
 *   descriptor 'fd', and will write it to the area pointed to by 'buf'.
 *
 ****************************************************************************/

static int userfs_fstat(FAR const struct file *filep, FAR struct stat *buf)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_fstat_request_s *req;
  FAR struct userfs_fstat_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_fstat_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_FSTAT;
  req->openinfo = filep->f_priv;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_fstat_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_fstat_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_fstat_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_FSTAT)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* Return the status of the directory entry */

  DEBUGASSERT(buf != NULL);
  memcpy(buf, &resp->buf, sizeof(struct stat));
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_fchstat
 *
 * Description:
 *   Change information about an open file associated with the file
 *   descriptor 'filep'.
 *
 ****************************************************************************/

static int userfs_fchstat(FAR const struct file *filep,
                          FAR const struct stat *buf, int flags)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_fchstat_request_s *req;
  FAR struct userfs_fchstat_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_fchstat_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_FCHSTAT;
  req->openinfo = filep->f_priv;
  req->buf      = *buf;
  req->flags    = flags;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_fchstat_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %zd\n", nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %zd\n", nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_fchstat_response_s))
    {
      ferr("ERROR: Response size incorrect: %zd\n", nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_fchstat_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_FCHSTAT)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_truncate
 *
 * Description:
 *   Set the size of the regular file referred to by 'filep' to 'length'
 *
 ****************************************************************************/

static int userfs_truncate(FAR struct file *filep, off_t length)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_truncate_request_s *req;
  FAR struct userfs_truncate_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(
              filep->f_inode != NULL &&
              filep->f_inode->i_private != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req           = (FAR struct userfs_truncate_request_s *)priv->iobuffer;
  req->req      = USERFS_REQ_TRUNCATE;
  req->openinfo = filep->f_priv;
  req->length   = length;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_truncate_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_truncate_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_truncate_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_FSTAT)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* Return the result of truncate operation */

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_opendir
 *
 * Description:
 *   Open a directory
 *
 ****************************************************************************/

static int userfs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
                          FAR struct fs_dirent_s **dir)
{
  FAR struct userfs_dir_s *udir;
  FAR struct userfs_state_s *priv;
  FAR struct userfs_opendir_request_s *req;
  FAR struct userfs_opendir_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_opendir_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_OPENDIR;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_OPENDIR_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_opendir_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_opendir_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_OPENDIR)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* Save the opaque dir reference in struct fs_dirent_s */

  DEBUGASSERT(dir != NULL);
  udir = fs_heap_zalloc(sizeof(struct userfs_dir_s));
  if (udir == NULL)
    {
      return -ENOMEM;
    }

  udir->dir = resp->dir;
  *dir = (FAR struct fs_dirent_s *)udir;
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_closedir
 *
 * Description:
 *   Close a directory
 *
 ****************************************************************************/

static int userfs_closedir(FAR struct inode *mountpt,
                           FAR struct fs_dirent_s *dir)
{
  FAR struct userfs_dir_s *udir;
  FAR struct userfs_state_s *priv;
  FAR struct userfs_closedir_request_s *req;
  FAR struct userfs_closedir_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;
  udir = (FAR struct userfs_dir_s *)dir;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_closedir_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_CLOSEDIR;
  req->dir = udir->dir;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_closedir_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_closedir_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_closedir_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_CLOSEDIR)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  fs_heap_free(udir);
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_readdir
 *
 * Description: Read the next directory entry
 *
 ****************************************************************************/

static int userfs_readdir(FAR struct inode *mountpt,
                          FAR struct fs_dirent_s *dir,
                          FAR struct dirent *entry)
{
  FAR struct userfs_dir_s *udir;
  FAR struct userfs_state_s *priv;
  FAR struct userfs_readdir_request_s *req;
  FAR struct userfs_readdir_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;
  udir = (FAR struct userfs_dir_s *)dir;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_readdir_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_READDIR;
  req->dir = udir->dir;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_readdir_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_readdir_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_readdir_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_READDIR)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* Return the dirent */

  DEBUGASSERT(dir != NULL);
  memcpy(entry, &resp->entry, sizeof(struct dirent));
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_rewindir
 *
 * Description: Reset directory read to the first entry
 *
 ****************************************************************************/

static int userfs_rewinddir(FAR struct inode *mountpt,
                            FAR struct fs_dirent_s *dir)
{
  FAR struct userfs_dir_s *udir;
  FAR struct userfs_state_s *priv;
  FAR struct userfs_rewinddir_request_s *req;
  FAR struct userfs_rewinddir_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;
  udir = (FAR struct userfs_dir_s *)dir;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_rewinddir_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_REWINDDIR;
  req->dir = udir->dir;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_rewinddir_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_rewinddir_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_rewinddir_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_REWINDDIR)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_bind
 *
 * Description: This implements a portion of the mount operation. This
 *  function allocates and initializes the mountpoint private data and
 *  binds the blockdriver inode to the filesystem private data.  The final
 *  binding of the private data (containing the blockdriver) to the
 *  mountpoint is performed by mount().
 *
 ****************************************************************************/

static int userfs_bind(FAR struct inode *blkdriver, FAR const void *data,
                       FAR void **handle)
{
  FAR struct userfs_state_s *priv;
  FAR const struct userfs_config_s *config;
  struct sockaddr_in client;
  unsigned int iolen;
  int ret;

  DEBUGASSERT(data != NULL && handle != NULL);
  config = (FAR const struct userfs_config_s *)data;

  /* Allocate an instance of the UserFS state structure */

  iolen = USERFS_REQ_MAXSIZE + config->mxwrite;
  priv  = fs_heap_malloc(SIZEOF_USERFS_STATE_S(iolen));
  if (priv == NULL)
    {
      ferr("ERROR: Failed to allocate state structure\n");
      return -ENOMEM;
    }

  /* Initialize the mutex that assures mutually exclusive access through
   * the entire request-response sequence.
   */

  nxmutex_init(&priv->lock);

  /* Copy the configuration data into the allocated structure.  Why?  First
   * we can't be certain of the life time of the memory underlying the config
   * reference.  Also, in the KERNEL build, the config data will like in
   * process-specific memory and cannot be shared across processes.
   */

  priv->mxwrite                = config->mxwrite;

  /* Preset the server address */

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

  /* Create a LocalHost UDP client socket */

  ret = psock_socket(PF_INET, SOCK_DGRAM, 0, &priv->psock);
  if (ret < 0)
    {
      ferr("ERROR: socket() failed: %d\n", ret);
      goto errout_with_alloc;
    }

  /* Bind the socket to the client address */

  client.sin_family      = AF_INET;
  client.sin_port        = 0;
  client.sin_addr.s_addr = HTONL(INADDR_LOOPBACK);

  ret = psock_bind(&priv->psock, (FAR struct sockaddr *)&client,
                   sizeof(struct sockaddr_in));
  if (ret < 0)
    {
      ferr("ERROR: bind() failed: %d\n", ret);
      goto errout_with_psock;
    }

  /* Mounted! */

  *handle = (FAR void *)priv;
  return OK;

errout_with_psock:
  psock_close(&priv->psock);

errout_with_alloc:
  nxmutex_destroy(&priv->lock);
  fs_heap_free(priv);
  return ret;
}

/****************************************************************************
 * Name: userfs_unbind
 *
 * Description: This implements the filesystem portion of the umount
 *   operation.
 *
 ****************************************************************************/

static int userfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
                         unsigned int flags)
{
  FAR struct userfs_state_s *priv = (FAR struct userfs_state_s *)handle;
  FAR struct userfs_destroy_request_s *req;
  FAR struct userfs_destroy_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(priv != NULL);

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_destroy_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_DESTROY;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_destroy_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_destroy_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_destroy_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_DESTROY)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* If the destruction failed, then refuse to unmount at this time */

  if (resp->ret < 0)
    {
      return resp->ret;
    }

  /* Free resources and return success */

  psock_close(&priv->psock);
  nxmutex_destroy(&priv->lock);
  fs_heap_free(priv);
  return OK;
}

/****************************************************************************
 * Name: userfs_statfs
 *
 * Description:
 *   Return filesystem statistics
 *
 ****************************************************************************/

static int userfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_statfs_request_s *req;
  FAR struct userfs_statfs_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_statfs_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_STATFS;

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       sizeof(struct userfs_statfs_request_s), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_statfs_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_statfs_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_STATFS)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* Return the status of the file system */

  DEBUGASSERT(buf != NULL);
  memcpy(buf, &resp->buf, sizeof(struct statfs));
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_unlink
 *
 * Description:
 *   Remove a directory entry
 *
 ****************************************************************************/

static int userfs_unlink(FAR struct inode *mountpt,
                         FAR const char *relpath)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_unlink_request_s *req;
  FAR struct userfs_unlink_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen     = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_unlink_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_UNLINK;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_UNLINK_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_unlink_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_unlink_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_UNLINK)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_mkdir
 *
 * Description:
 *   Create a new directory
 *
 ****************************************************************************/

static int userfs_mkdir(FAR struct inode *mountpt,
                        FAR const char *relpath, mode_t mode)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_mkdir_request_s *req;
  FAR struct userfs_mkdir_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req       = (FAR struct userfs_mkdir_request_s *)priv->iobuffer;
  req->req  = USERFS_REQ_MKDIR;
  req->mode = mode;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_MKDIR_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_mkdir_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_mkdir_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_MKDIR)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_rmdir
 *
 * Description:
 *   Remove a directory
 *
 ****************************************************************************/

static int userfs_rmdir(FAR struct inode *mountpt,
                        FAR const char *relpath)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_rmdir_request_s *req;
  FAR struct userfs_rmdir_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_rmdir_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_RMDIR;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_RMDIR_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_rmdir_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_rmdir_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_RMDIR)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_rename
 *
 * Description:
 *   Rename a directory entry
 *
 ****************************************************************************/

static int userfs_rename(FAR struct inode *mountpt,
                         FAR const char *oldrelpath,
                         FAR const char *newrelpath)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_rename_request_s *req;
  FAR struct userfs_rename_response_s *resp;
  int oldpathlen;
  int newpathlen;
  ssize_t nsent;
  ssize_t nrecvd;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path lengths */

  DEBUGASSERT(oldrelpath != NULL && newrelpath != NULL);
  oldpathlen = strlen(oldrelpath) + 1;
  newpathlen = strlen(newrelpath) + 1;

  if ((oldpathlen + newpathlen) > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req            = (FAR struct userfs_rename_request_s *)priv->iobuffer;
  req->req       = USERFS_REQ_RENAME;
  req->newoffset = oldpathlen;

  strlcpy(req->oldrelpath, oldrelpath, oldpathlen);
  strlcpy(&req->oldrelpath[oldpathlen], newrelpath, newpathlen);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                      SIZEOF_USERFS_RENAME_REQUEST_S(oldpathlen, newpathlen),
                      0, (FAR struct sockaddr *)&priv->server,
                      sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_rename_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_rename_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_RENAME)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

/****************************************************************************
 * Name: userfs_stat
 *
 * Description:
 *   Return information about a file or directory
 *
 ****************************************************************************/

static int userfs_stat(FAR struct inode *mountpt, FAR const char *relpath,
                       FAR struct stat *buf)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_stat_request_s *req;
  FAR struct userfs_stat_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req      = (FAR struct userfs_stat_request_s *)priv->iobuffer;
  req->req = USERFS_REQ_STAT;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_STAT_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %d\n", (int)nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %d\n", (int)nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_stat_response_s))
    {
      ferr("ERROR: Response size incorrect: %u\n", (unsigned int)nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_stat_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_STAT)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  /* Return the directory entry status */

  DEBUGASSERT(buf != NULL);
  memcpy(buf, &resp->buf, sizeof(struct stat));
  return resp->ret;
}

/****************************************************************************
 * Name: userfs_chstat
 *
 * Description:
 *   Change information about a file or directory
 *
 ****************************************************************************/

static int userfs_chstat(FAR struct inode *mountpt, FAR const char *relpath,
                         FAR const struct stat *buf, int flags)
{
  FAR struct userfs_state_s *priv;
  FAR struct userfs_chstat_request_s *req;
  FAR struct userfs_chstat_response_s *resp;
  ssize_t nsent;
  ssize_t nrecvd;
  int pathlen;
  int ret;

  DEBUGASSERT(mountpt != NULL &&
              mountpt->i_private != NULL);
  priv = mountpt->i_private;

  /* Check the path length */

  DEBUGASSERT(relpath != NULL);
  pathlen = strlen(relpath);
  if (pathlen > priv->mxwrite)
    {
      return -E2BIG;
    }

  /* Get exclusive access */

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Construct and send the request to the server */

  req        = (FAR struct userfs_chstat_request_s *)priv->iobuffer;
  req->req   = USERFS_REQ_CHSTAT;
  req->buf   = *buf;
  req->flags = flags;

  strlcpy(req->relpath, relpath, priv->mxwrite);

  nsent = psock_sendto(&priv->psock, priv->iobuffer,
                       SIZEOF_USERFS_CHSTAT_REQUEST_S(pathlen + 1), 0,
                       (FAR struct sockaddr *)&priv->server,
                       sizeof(struct sockaddr_in));
  if (nsent < 0)
    {
      ferr("ERROR: psock_sendto failed: %zd\n", nsent);
      nxmutex_unlock(&priv->lock);
      return (int)nsent;
    }

  /* Then get the response from the server */

  nrecvd = psock_recvfrom(&priv->psock, priv->iobuffer, IOBUFFER_SIZE(priv),
                          0, NULL, NULL);
  nxmutex_unlock(&priv->lock);

  if (nrecvd < 0)
    {
      ferr("ERROR: psock_recvfrom failed: %zd\n", nrecvd);
      return (int)nrecvd;
    }

  if (nrecvd != sizeof(struct userfs_chstat_response_s))
    {
      ferr("ERROR: Response size incorrect: %zd\n", nrecvd);
      return -EIO;
    }

  resp = (FAR struct userfs_chstat_response_s *)priv->iobuffer;
  if (resp->resp != USERFS_RESP_STAT)
    {
      ferr("ERROR: Incorrect response: %u\n", resp->resp);
      return -EIO;
    }

  return resp->ret;
}

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