/****************************************************************************
 * fs/procfs/fs_procfs.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 <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/sched.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/procfs.h>

#include "mount/mount.h"

/****************************************************************************
 * External Definitions
 ****************************************************************************/

extern const struct procfs_operations g_clk_operations;
extern const struct procfs_operations g_cpuinfo_operations;
extern const struct procfs_operations g_cpuload_operations;
extern const struct procfs_operations g_critmon_operations;
extern const struct procfs_operations g_fdt_operations;
extern const struct procfs_operations g_iobinfo_operations;
extern const struct procfs_operations g_irq_operations;
extern const struct procfs_operations g_meminfo_operations;
extern const struct procfs_operations g_memdump_operations;
extern const struct procfs_operations g_mempool_operations;
extern const struct procfs_operations g_module_operations;
extern const struct procfs_operations g_pm_operations;
extern const struct procfs_operations g_proc_operations;
extern const struct procfs_operations g_tcbinfo_operations;
extern const struct procfs_operations g_uptime_operations;
extern const struct procfs_operations g_version_operations;

/* This is not good.  These are implemented in other sub-systems.  Having to
 * deal with them here is not a good coupling. What is really needed is a
 * run-time procfs registration system vs. a build time, fixed procfs
 * configuration.
 */

extern const struct procfs_operations g_mount_operations;
extern const struct procfs_operations g_net_operations;
extern const struct procfs_operations g_netroute_operations;
extern const struct procfs_operations g_part_operations;
extern const struct procfs_operations g_smartfs_operations;

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

/* Table of all known / pre-registered procfs handlers / participants. */

#ifdef CONFIG_FS_PROCFS_REGISTER
static const struct procfs_entry_s g_base_entries[] =
#else
static const struct procfs_entry_s g_procfs_entries[] =
#endif
{
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
  { "[0-9]*/**",    &g_proc_operations,     PROCFS_UNKOWN_TYPE },
  { "[0-9]*",       &g_proc_operations,     PROCFS_DIR_TYPE    },
#endif

#if defined(CONFIG_CLK) && !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK)
  { "clk",          &g_clk_operations,      PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_ARCH_HAVE_CPUINFO) && !defined(CONFIG_FS_PROCFS_EXCLUDE_CPUINFO)
  { "cpuinfo",      &g_cpuinfo_operations,  PROCFS_FILE_TYPE   },
#endif

#if !defined(CONFIG_SCHED_CPULOAD_NONE) && \
    !defined(CONFIG_FS_PROCFS_EXCLUDE_CPULOAD)
  { "cpuload",      &g_cpuload_operations,  PROCFS_FILE_TYPE   },
#endif

#ifdef CONFIG_SCHED_CRITMONITOR
  { "critmon",      &g_critmon_operations,  PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_DEVICE_TREE) && !defined(CONFIG_FS_PROCFS_EXCLUDE_FDT)
  { "fdt",          &g_fdt_operations,      PROCFS_FILE_TYPE   },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_BLOCKS
  { "fs/blocks",    &g_mount_operations,    PROCFS_FILE_TYPE   },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_MOUNT
  { "fs/mount",     &g_mount_operations,    PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_FS_SMARTFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
  { "fs/smartfs**", &g_smartfs_operations,  PROCFS_UNKOWN_TYPE },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_USAGE
  { "fs/usage",     &g_mount_operations,    PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_MM_IOB) && !defined(CONFIG_FS_PROCFS_EXCLUDE_IOBINFO)
  { "iobinfo",      &g_iobinfo_operations,  PROCFS_FILE_TYPE   },
#endif

#ifdef CONFIG_SCHED_IRQMONITOR
  { "irqs",         &g_irq_operations,      PROCFS_FILE_TYPE   },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_MEMINFO
#  ifndef CONFIG_FS_PROCFS_EXCLUDE_MEMDUMP
  { "memdump",      &g_memdump_operations,  PROCFS_FILE_TYPE   },
#  endif
  { "meminfo",      &g_meminfo_operations,  PROCFS_FILE_TYPE   },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_MEMPOOL
  { "mempool",      &g_mempool_operations,  PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_MODULE) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
  { "modules",      &g_module_operations,   PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_NET) && !defined(CONFIG_FS_PROCFS_EXCLUDE_NET)
  { "net",          &g_net_operations,      PROCFS_DIR_TYPE    },
#  if defined(CONFIG_NET_ROUTE) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ROUTE)
  { "net/route",    &g_netroute_operations, PROCFS_DIR_TYPE    },
  { "net/route/**", &g_netroute_operations, PROCFS_UNKOWN_TYPE },
#  endif
  { "net/**",       &g_net_operations,      PROCFS_UNKOWN_TYPE },
#endif

#if defined(CONFIG_MTD_PARTITION) && !defined(CONFIG_FS_PROCFS_EXCLUDE_PARTITIONS)
  { "partitions",   &g_part_operations,     PROCFS_FILE_TYPE   },
#endif

#if defined(CONFIG_PM) && defined(CONFIG_PM_PROCFS)
  { "pm",           &g_pm_operations,       PROCFS_DIR_TYPE    },
  { "pm/**",        &g_pm_operations,       PROCFS_UNKOWN_TYPE },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
  { "self",         &g_proc_operations,     PROCFS_DIR_TYPE    },
  { "self/**",      &g_proc_operations,     PROCFS_UNKOWN_TYPE },
#endif

#if defined(CONFIG_ARCH_HAVE_TCBINFO) && !defined(CONFIG_FS_PROCFS_EXCLUDE_TCBINFO)
  { "tcbinfo",      &g_tcbinfo_operations,  PROCFS_FILE_TYPE   },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_UPTIME
  { "uptime",       &g_uptime_operations,   PROCFS_FILE_TYPE   },
#endif

#ifndef CONFIG_FS_PROCFS_EXCLUDE_VERSION
  { "version",      &g_version_operations,  PROCFS_FILE_TYPE   },
#endif
};

#ifdef CONFIG_FS_PROCFS_REGISTER
static const uint8_t g_base_entrycount = sizeof(g_base_entries) /
                                         sizeof(struct procfs_entry_s);

static FAR struct procfs_entry_s *g_procfs_entries;
static uint8_t g_procfs_entrycount;
#else
static const uint8_t g_procfs_entrycount = sizeof(g_procfs_entries) /
                                           sizeof(struct procfs_entry_s);
#endif

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

/* File system methods */

static int     procfs_open(FAR struct file *filep, FAR const char *relpath,
                 int oflags, mode_t mode);
static int     procfs_close(FAR struct file *filep);
static ssize_t procfs_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);
static ssize_t procfs_write(FAR struct file *filep, FAR const char *buffer,
                 size_t buflen);
static int     procfs_poll(FAR struct file *filep, FAR struct pollfd *fds,
                 bool setup);
static int     procfs_ioctl(FAR struct file *filep, int cmd,
                 unsigned long arg);

static int     procfs_dup(FAR const struct file *oldp,
                 FAR struct file *newp);
static int     procfs_fstat(FAR const struct file *filep,
                 FAR struct stat *buf);

static int     procfs_opendir(FAR struct inode *mountpt,
                 FAR const char *relpath, FAR struct fs_dirent_s **dir);
static int     procfs_closedir(FAR struct inode *mountpt,
                 FAR struct fs_dirent_s *dir);
static int     procfs_readdir(FAR struct inode *mountpt,
                 FAR struct fs_dirent_s *dir, FAR struct dirent *entry);
static int     procfs_rewinddir(FAR struct inode *mountpt,
                 FAR struct fs_dirent_s *dir);

static int     procfs_bind(FAR struct inode *blkdriver,
                 FAR const void *data, FAR void **handle);
static int     procfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
                 unsigned int flags);
static int     procfs_statfs(FAR struct inode *mountpt,
                 FAR struct statfs *buf);

static int     procfs_stat(FAR struct inode *mountpt,
                 FAR const char *relpath, FAR struct stat *buf);

/* Initialization */

#ifdef CONFIG_FS_PROCFS_REGISTER
static int     procfs_initialize(void);
#endif

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

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

const struct mountpt_operations g_procfs_operations =
{
  procfs_open,       /* open */
  procfs_close,      /* close */
  procfs_read,       /* read */
  procfs_write,      /* write */
  NULL,              /* seek */
  procfs_ioctl,      /* ioctl */
  NULL,              /* mmap */
  NULL,              /* truncate */
  procfs_poll,       /* poll */

  NULL,              /* sync */
  procfs_dup,        /* dup */
  procfs_fstat,      /* fstat */
  NULL,              /* fchstat */

  procfs_opendir,    /* opendir */
  procfs_closedir,   /* closedir */
  procfs_readdir,    /* readdir */
  procfs_rewinddir,  /* rewinddir */

  procfs_bind,       /* bind */
  procfs_unbind,     /* unbind */
  procfs_statfs,     /* statfs */

  NULL,              /* unlink */
  NULL,              /* mkdir */
  NULL,              /* rmdir */
  NULL,              /* rename */
  procfs_stat,       /* stat */
  NULL               /* chstat */
};

/* Level 0 contains the directory of active tasks in addition to other
 * statically registered entries with custom handlers.  This structure
 * contains a snapshot of the active tasks when the directory is first
 * opened.
 */

struct procfs_level0_s
{
  struct procfs_dir_priv_s base;         /* Base struct for ProcFS dir */

  /* Our private data */

  uint8_t lastlen;                       /* length of last reported static dir */
  pid_t pid[CONFIG_FS_PROCFS_MAX_TASKS]; /* Snapshot of all active task IDs */
  FAR const char *lastread;              /* Pointer to last static dir read */
};

/* Level 1 is an internal virtual directory (such as /proc/fs) which
 * will contain one or more additional static entries based on the
 * configuration.
 */

struct procfs_level1_s
{
  struct procfs_dir_priv_s base;     /* Base struct for ProcFS dir */

  /* Our private data */

  uint8_t lastlen;                   /* length of last reported static dir */
  uint8_t subdirlen;                 /* Length of the subdir search */
  uint16_t firstindex;               /* Index of 1st entry matching this subdir */
  FAR const char *lastread;          /* Pointer to last static dir read */
};

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

#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS

/****************************************************************************
 * Name: procfs_enum
 ****************************************************************************/

static void procfs_enum(FAR struct tcb_s *tcb, FAR void *arg)
{
  FAR struct procfs_level0_s *dir = (FAR struct procfs_level0_s *)arg;
  int index;

  DEBUGASSERT(dir);

  /* Add the PID to the list */

  index = dir->base.nentries;
  if (index >= CONFIG_FS_PROCFS_MAX_TASKS)
    {
      return;
    }

  dir->pid[index] = tcb->pid;
  dir->base.nentries = index + 1;
}

/****************************************************************************
 * Name: procfs_sort_pid
 ****************************************************************************/

static void procfs_sort_pid(FAR struct procfs_level0_s *level0)
{
  pid_t pid;
  int i;
  int j;

  /* Sort the process id by Bubble.
   * FIXME: improve searching algorithm.
   */

  for (i = 0; i < level0->base.nentries; i++)
    {
      for (j = 0; j < level0->base.nentries - 1 - i; j++)
        {
          if (level0->pid[j] > level0->pid[j + 1])
            {
              pid = level0->pid[j];
              level0->pid[j] = level0->pid[j + 1];
              level0->pid[j + 1] = pid;
            }
        }
    }
}
#endif

/****************************************************************************
 * Name: procfs_open
 ****************************************************************************/

static int procfs_open(FAR struct file *filep, FAR const char *relpath,
                       int oflags, mode_t mode)
{
  int x;
  int ret = -ENOENT;

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

  /* Perform the stat based on the procfs_entry operations */

  for (x = 0; x < g_procfs_entrycount; x++)
    {
      /* Test if the path matches this entry's specification */

      if (fnmatch(g_procfs_entries[x].pathpattern, relpath, 0) == 0)
        {
          /* Match found!  Stat using this procfs entry */

          DEBUGASSERT(g_procfs_entries[x].ops &&
                      g_procfs_entries[x].ops->open);

          ret = g_procfs_entries[x].ops->open(filep, relpath, oflags, mode);
          if (ret == OK)
            {
              DEBUGASSERT(filep->f_priv);

              ((FAR struct procfs_file_s *)filep->f_priv)->procfsentry =
                                    &g_procfs_entries[x];
              break;
            }
        }
    }

  return ret;
}

/****************************************************************************
 * Name: procfs_close
 ****************************************************************************/

static int procfs_close(FAR struct file *filep)
{
  FAR struct procfs_file_s *attr;
  int ret = OK;

  /* Recover our private data from the struct file instance */

  attr = (FAR struct procfs_file_s *)filep->f_priv;
  DEBUGASSERT(attr);

  /* Release the file attributes structure */

  if (attr->procfsentry->ops->close != NULL)
    {
      ret = attr->procfsentry->ops->close(filep);
    }
  else
    {
      kmm_free(attr);
    }

  filep->f_priv = NULL;
  return ret;
}

/****************************************************************************
 * Name: procfs_read
 ****************************************************************************/

static ssize_t procfs_read(FAR struct file *filep, FAR char *buffer,
                           size_t buflen)
{
  FAR struct procfs_file_s *handler;

  finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);

  /* Recover our private data from the struct file instance */

  handler = (FAR struct procfs_file_s *)filep->f_priv;
  DEBUGASSERT(handler);

  /* Call the handler's read routine */

  return handler->procfsentry->ops->read(filep, buffer, buflen);
}

/****************************************************************************
 * Name: procfs_write
 ****************************************************************************/

static ssize_t procfs_write(FAR struct file *filep, FAR const char *buffer,
                            size_t buflen)
{
  FAR struct procfs_file_s *handler;

  finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);

  /* Recover our private data from the struct file instance */

  handler = (FAR struct procfs_file_s *)filep->f_priv;
  DEBUGASSERT(handler);

  /* Call the handler's write routine */

  if (handler->procfsentry->ops->write)
    {
      return handler->procfsentry->ops->write(filep, buffer, buflen);
    }

  return 0;
}

static int procfs_poll(FAR struct file *filep, FAR struct pollfd *fds,
                       bool setup)
{
  FAR struct procfs_file_s *handler;

  finfo("fds=%p setup=%d\n", fds, setup);

  /* Recover our private data from the struct file instance */

  handler = (FAR struct procfs_file_s *)filep->f_priv;
  DEBUGASSERT(handler);

  /* Call the handler's poll routine */

  if (handler->procfsentry->ops->poll)
    {
      return handler->procfsentry->ops->poll(filep, fds, setup);
    }

  return -ENOSYS;
}

/****************************************************************************
 * Name: procfs_ioctl
 ****************************************************************************/

static int procfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  finfo("cmd: %d arg: %08lx\n", cmd, arg);

  /* No IOCTL commands supported */

  return -ENOTTY;
}

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

static int procfs_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct procfs_file_s *oldattr;

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

  /* Recover our private data from the old struct file instance */

  oldattr = (FAR struct procfs_file_s *)oldp->f_priv;
  DEBUGASSERT(oldattr);

  /* Allow lower-level handler do the dup to get it's extra data */

  return oldattr->procfsentry->ops->dup(oldp, newp);
}

/****************************************************************************
 * Name: procfs_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 procfs_fstat(FAR const struct file *filep, FAR struct stat *buf)
{
  FAR struct procfs_file_s *handler;

  finfo("buf=%p\n", buf);

  /* Recover our private data from the struct file instance */

  handler = (FAR struct procfs_file_s *)filep->f_priv;
  DEBUGASSERT(handler);

  /* The procfs file system contains only directory and data file entries.
   * Since the file has been opened, we know that this is a data file and,
   * at a minimum, readable.
   */

  memset(buf, 0, sizeof(struct stat));
  buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;

  /* If the write method is provided, then let's also claim that the file is
   * writable.
   */

  if (handler->procfsentry->ops->write != NULL)
    {
      buf->st_mode |= S_IWOTH | S_IWGRP | S_IWUSR;
    }

  return OK;
}

/****************************************************************************
 * Name: procfs_opendir
 *
 * Description:
 *   Open a directory for read access
 *
 ****************************************************************************/

static int procfs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
                          FAR struct fs_dirent_s **dir)
{
  FAR struct procfs_level0_s *level0;

  finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
  DEBUGASSERT(mountpt && dir && relpath);

  /* The relative must be either:
   *
   * ""      - The top level directory of task/thread IDs
   * "<pid>" - The sub-directory of task/thread attributes
   */

  if (!relpath || relpath[0] == '\0')
    {
      /* The path refers to the top level directory.  Allocate the level0
       * dirent structure.
       */

      level0 = (FAR struct procfs_level0_s *)
         kmm_zalloc(sizeof(struct procfs_level0_s));
      if (!level0)
        {
          ferr("ERROR: Failed to allocate the level0 directory structure\n");
          return -ENOMEM;
        }

      /* Take a snapshot of all currently active tasks.  Any new tasks
       * added between the opendir() and closedir() call will not be
       * visible.
       *
       * NOTE that interrupts must be disabled throughout the traversal.
       */

#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
      nxsched_foreach(procfs_enum, level0);
      procfs_sort_pid(level0);
#else
      level0->base.index = 0;
      level0->base.nentries = 0;
#endif

      /* Initialize lastread entries */

      level0->lastread = "";
      level0->lastlen = 0;
      level0->base.procfsentry = NULL;

      *dir = (FAR struct fs_dirent_s *)level0;
    }
  else
    {
      int x;
      int ret;
      int len = strlen(relpath);

      /* Search the static array of procfs_entries */

      for (x = 0; x < g_procfs_entrycount; x++)
        {
          /* Test if the path matches this entry's specification */

          if (fnmatch(g_procfs_entries[x].pathpattern, relpath, 0) == 0)
            {
              /* Match found!  Call the handler's opendir routine.  If
               * successful, this opendir routine will create an entry
               * derived from struct procfs_dir_priv_s as dir.
               */

              DEBUGASSERT(g_procfs_entries[x].ops != NULL &&
                          g_procfs_entries[x].ops->opendir != NULL);

              ret = g_procfs_entries[x].ops->opendir(relpath, dir);
              if (ret == OK)
                {
                  FAR struct procfs_dir_priv_s *dirpriv;

                  DEBUGASSERT(*dir);

                  /* Set the procfs_entry handler */

                  dirpriv = (FAR struct procfs_dir_priv_s *)(*dir);
                  dirpriv->procfsentry = &g_procfs_entries[x];
                }

              return ret;
            }

          /* Test for a sub-string match (e.g. "ls /proc/fs") */

          else if (strncmp(g_procfs_entries[x].pathpattern, relpath,
                           len) == 0)
            {
              FAR struct procfs_level1_s *level1;

              /* Doing an intermediate directory search */

              /* The path refers to the top level directory.  Allocate
               * the level1 dirent structure.
               */

              level1 = (FAR struct procfs_level1_s *)
                 kmm_zalloc(sizeof(struct procfs_level1_s));
              if (!level1)
                {
                  ferr("ERROR: Failed to allocate the level0 directory "
                       "structure\n");
                  return -ENOMEM;
                }

              level1->base.level = 1;
              level1->base.index = x;
              level1->firstindex = x;
              level1->subdirlen = len;
              level1->lastread = "";
              level1->lastlen = 0;
              level1->base.procfsentry = NULL;

              *dir = (FAR struct fs_dirent_s *)level1;
              break;
            }
        }

      if (x == g_procfs_entrycount)
        {
          return -ENOENT;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: procfs_closedir
 *
 * Description: Close the directory listing
 *
 ****************************************************************************/

static int procfs_closedir(FAR struct inode *mountpt,
                           FAR struct fs_dirent_s *dir)
{
  DEBUGASSERT(mountpt && dir);
  kmm_free(dir);
  return OK;
}

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

static int procfs_readdir(FAR struct inode *mountpt,
                          FAR struct fs_dirent_s *dir,
                          FAR struct dirent *entry)
{
  FAR const struct procfs_entry_s *pentry = NULL;
  FAR struct procfs_dir_priv_s *priv;
  FAR struct procfs_level0_s *level0;
  FAR const char *name = NULL;
  unsigned int index;
  int ret = -ENOENT;

  DEBUGASSERT(mountpt && dir);
  priv = (FAR struct procfs_dir_priv_s *)dir;

  /* Are we reading the 1st directory level with dynamic PID and static
   * entries?
   */

  if (priv->level == 0)
    {
      level0 = (FAR struct procfs_level0_s *)priv;

      /* Have we reached the end of the PID information */

      index = priv->index;
      if (index >= priv->nentries)
        {
          index -= priv->nentries;

          /* We must report the next static entry ... no more PID entries.
           * skip any entries with wildcards in the first segment of the
           * directory name.
           */

          while (index < g_procfs_entrycount)
            {
              pentry = &g_procfs_entries[index];
              name = pentry->pathpattern;

              while (*name != '/' && *name != '\0')
                {
                  if (*name == '*' || *name == '[' || *name == '?')
                    {
                      /* Wildcard found.  Skip this entry */

                      index++;
                      name = NULL;
                      break;
                    }

                  name++;
                }

              /* Test if we skipped this entry */

              if (name != NULL)
                {
                  /* This entry is okay to report. Test if it has a
                   * duplicate first level name as the one we just reported.
                   * This could happen in the event of procfs_entry_s such
                   * as:
                   *
                   *    fs/smartfs
                   *    fs/nfs
                   *    fs/nxffs
                   */

                  name = g_procfs_entries[index].pathpattern;
                  if (!level0->lastlen ||
                      strncmp(name, level0->lastread, level0->lastlen) != 0)
                    {
                      /* Not a duplicate, return the first segment of this
                       * entry
                       */

                      break;
                    }
                  else
                    {
                      /* Skip this entry ... duplicate 1st level name found */

                      index++;
                    }
                }
            }

          /* Test if we are at the end of the directory */

          if (index < g_procfs_entrycount)
            {
              /* Report the next static entry */

              level0->lastlen = strcspn(name, "/");
              level0->lastread = name;
              strlcpy(entry->d_name, name, level0->lastlen + 1);

              /* If the entry is a directory type OR if the reported name is
               * only a sub-string of the entry (meaning that it contains
               * '/'), then report this entry as a directory.
               */

              if (pentry->type == PROCFS_DIR_TYPE ||
                  level0->lastlen != strlen(name))
                {
                  entry->d_type = DTYPE_DIRECTORY;
                }
              else
                {
                  entry->d_type = DTYPE_FILE;
                }

              /* Advance to next entry for the next read */

              priv->index = priv->nentries + index;
              ret = OK;
            }
        }
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
      else
        {
          /* Verify that the pid still refers to an active task/thread */

          pid_t pid = level0->pid[index];
          FAR struct tcb_s *tcb = nxsched_get_tcb(pid);
          if (!tcb)
            {
              ferr("ERROR: PID %d is no longer valid\n", pid);
              return -ENOENT;
            }

          /* Save the filename=pid and file type=directory */

          entry->d_type = DTYPE_DIRECTORY;
          procfs_snprintf(entry->d_name, NAME_MAX + 1, "%d", pid);

          /* Set up the next directory entry offset.  NOTE that we could use
           * the standard f_pos instead of our own private index.
           */

          level0->base.index = index + 1;
          ret = OK;
        }
#endif /* CONFIG_FS_PROCFS_EXCLUDE_PROCESS */
    }

  /* Are we reading an intermediate subdirectory? */

  else if (priv->level > 0 && priv->procfsentry == NULL)
    {
      FAR struct procfs_level1_s *level1;

      level1 = (FAR struct procfs_level1_s *)priv;

      /* Test if this entry matches.  We assume all entries of the same
       * subdirectory are listed in order in the procfs_entry array.
       */

      if (level1->base.index < g_procfs_entrycount &&
          level1->firstindex < g_procfs_entrycount &&
          strncmp(g_procfs_entries[level1->base.index].pathpattern,
                  g_procfs_entries[level1->firstindex].pathpattern,
                  level1->subdirlen) == 0)
        {
          /* This entry matches.  Report the subdir entry */

          name = &g_procfs_entries[level1->base.index].
                    pathpattern[level1->subdirlen + 1];
          level1->lastlen = strcspn(name, "/");
          level1->lastread = name;
          strlcpy(entry->d_name, name, level1->lastlen + 1);

          /* Some of the search entries contain '**' wildcards.  When we
           * report the entry name, we must remove this wildcard search
           * specifier.
           */

          while (entry->d_name[level1->lastlen - 1] == '*')
            {
              level1->lastlen--;
            }

          entry->d_name[level1->lastlen] = '\0';

          if (name[level1->lastlen] == '/')
            {
              entry->d_type = DTYPE_DIRECTORY;
            }
          else
            {
              entry->d_type = DTYPE_FILE;
            }

          level1->base.index++;
          ret = OK;
        }
    }
  else
    {
      /* We are performing a directory search of one of the subdirectories
       * and we must let the handler perform the read.
       */

      DEBUGASSERT(priv->procfsentry && priv->procfsentry->ops->readdir);
      ret = priv->procfsentry->ops->readdir(dir, entry);
    }

  return ret;
}

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

static int procfs_rewinddir(FAR struct inode *mountpt,
                            FAR struct fs_dirent_s *dir)
{
  FAR struct procfs_dir_priv_s *priv;

  DEBUGASSERT(mountpt && dir);
  priv = (FAR struct procfs_dir_priv_s *)dir;

  if (priv->level > 0 && priv->procfsentry == NULL)
    {
      priv->index = ((FAR struct procfs_level1_s *)priv)->firstindex;
    }
  else
    {
      priv->index = 0;
    }

  return OK;
}

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

static int procfs_bind(FAR struct inode *blkdriver, FAR const void *data,
                       FAR void **handle)
{
#ifdef CONFIG_FS_PROCFS_REGISTER
  /* Make sure that we are properly initialized */

  procfs_initialize();
#endif

  return OK;
}

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

static int procfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
                         unsigned int flags)
{
  return OK;
}

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

static int procfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
{
  /* Fill in the statfs info */

  buf->f_type    = PROCFS_MAGIC;
  buf->f_bsize   = 0;
  buf->f_blocks  = 0;
  buf->f_bfree   = 0;
  buf->f_bavail  = 0;
  buf->f_namelen = NAME_MAX;
  return OK;
}

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

static int procfs_stat(FAR struct inode *mountpt, FAR const char *relpath,
                       FAR struct stat *buf)
{
  int ret = -ENOENT;

  /* Three path forms are accepted:
   *
   * ""      - The relative path refers to the top level directory
   * "<pid>" - If <pid> refers to a currently active task/thread, then it
   *   is a directory
   * "<pid>/<attr>" - If <attr> is a recognized attribute then, then it
   *   is a file.
   */

  memset(buf, 0, sizeof(struct stat));
  if (!relpath || relpath[0] == '\0')
    {
      /* The path refers to the top level directory.
       * It's a read-only directory.
       */

      buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
      ret = OK;
    }
  else
    {
      int x;
      int len = strlen(relpath);

      /* Perform the stat based on the procfs_entry operations */

      for (x = 0; x < g_procfs_entrycount; x++)
        {
          /* Test if the path matches this entry's specification */

          if (fnmatch(g_procfs_entries[x].pathpattern, relpath, 0) == 0)
            {
              /* Match found!  Stat using this procfs entry */

              DEBUGASSERT(g_procfs_entries[x].ops &&
                          g_procfs_entries[x].ops->stat);

              return g_procfs_entries[x].ops->stat(relpath, buf);
            }

          /* Test for an internal subdirectory stat */

          else if (strncmp(g_procfs_entries[x].pathpattern, relpath,
                           len) == 0)
            {
              /* It's an internal subdirectory */

              buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
              ret = OK;
              break;
            }
        }
    }

  return ret;
}

/****************************************************************************
 * Name: procfs_initialize
 *
 * Description:
 *   Configure the initial set of entries in the procfs file system.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure
 *
 ****************************************************************************/

#ifdef CONFIG_FS_PROCFS_REGISTER
int procfs_initialize(void)
{
  /* Are we already initialized? */

  if (g_procfs_entries == NULL)
    {
      /* No.. allocate a modifiable list of entries */

      g_procfs_entries = (FAR struct procfs_entry_s *)
        kmm_malloc(sizeof(g_base_entries));
      if (g_procfs_entries == NULL)
        {
          return -ENOMEM;
        }

      /* And copy the fixed entries into the allocated array */

      memcpy(g_procfs_entries, g_base_entries, sizeof(g_base_entries));
      g_procfs_entrycount = g_base_entrycount;
    }

  return OK;
}
#endif

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

/****************************************************************************
 * Name: procfs_register
 *
 * Description:
 *   Add a new entry to the procfs file system.
 *
 *   NOTE: This function should be called *prior* to mounting the procfs
 *   file system to prevent concurrency problems with the modification of
 *   the procfs data set while it is in use.
 *
 * Input Parameters:
 *   entry - Describes the entry to be registered.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure
 *
 ****************************************************************************/

#ifdef CONFIG_FS_PROCFS_REGISTER
int procfs_register(FAR const struct procfs_entry_s *entry)
{
  FAR struct procfs_entry_s *newtable;
  unsigned int newcount;
  size_t newsize;
  int ret = -ENOMEM;

  /* Make sure that we are properly initialized */

  procfs_initialize();

  /* realloc the table of procfs entries.
   *
   * REVISIT:  This reallocation may free memory previously used for the
   * procfs entry table.  If that table were actively in use, then that
   * could cause procfs logic to use a stale memory pointer!  We avoid that
   * problem by requiring that the procfs file be unmounted when the new
   * entry is added.  That requirement, however, is not enforced explicitly.
   *
   * Locking the scheduler as done below is insufficient.  As would be just
   * marking the entries as volatile.
   */

  newcount = g_procfs_entrycount + 1;
  newsize  = newcount * sizeof(struct procfs_entry_s);

  newtable = (FAR struct procfs_entry_s *)
    kmm_realloc(g_procfs_entries, newsize);
  if (newtable != NULL)
    {
      /* Copy the new entry at the end of the reallocated table */

      memcpy(&newtable[g_procfs_entrycount], entry,
             sizeof(struct procfs_entry_s));

      /* Instantiate the reallocated table */

      g_procfs_entries    = newtable;
      g_procfs_entrycount = newcount;
      ret = OK;
    }

  return ret;
}
#endif