/****************************************************************************
 * fs/procfs/fs_procfsproc.c
 *
 *   Copyright (C) 2013-2015 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * 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 <assert.h>
#include <errno.h>
#include <debug.h>

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

#ifdef CONFIG_SCHED_CPULOAD
#  include <nuttx/clock.h>
#endif

#include <arch/irq.h>

#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS)
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */

#define STATUS_LINELEN 32

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/
/* This enumeration identifies all of the task/thread nodes that can be
 * accessed via the procfs file system.
 */

enum proc_node_e
{
  PROC_LEVEL0 = 0,                    /* The top-level directory */
  PROC_STATUS,                        /* Task/thread status */
  PROC_CMDLINE,                       /* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
  PROC_LOADAVG,                       /* Average CPU utilization */
#endif
  PROC_STACK,                         /* Task stack info */
  PROC_GROUP,                         /* Group directory */
  PROC_GROUP_STATUS,                  /* Task group status */
  PROC_GROUP_FD                       /* Group file descriptors */
};

/* This structure associates a relative path name with an node in the task
 * procfs
 */

struct proc_node_s
{
  FAR const char *relpath;            /* Relative path to the node */
  FAR const char *name;               /* Terminal node segment name */
  uint8_t node;                       /* Type of node (see enum proc_node_e) */
  uint8_t dtype;                      /* dirent type (see include/dirent.h) */
};

/* This structure describes one open "file" */

struct proc_file_s
{
  struct procfs_file_s base;          /* Base open file structure */
  FAR const struct proc_node_s *node; /* Describes the file node */
  pid_t pid;                          /* Task/thread ID */
  char line[STATUS_LINELEN];          /* Pre-allocated buffer for formatted lines */
};

/* This structure describes one open "directory" */

struct proc_dir_s
{
  struct procfs_dir_priv_s  base;     /* Base directory private data */
  FAR const struct proc_node_s *node; /* Directory node description */
  pid_t pid;                       /* ID of task/thread for attributes */
};

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

static FAR const char *g_policy[4] =
{
  "SCHED_FIFO", "SCHED_RR", "SCHED_SPORADIC", "SCHED_OTHER"
};

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

static FAR const struct proc_node_s *
               proc_findnode(FAR const char *relpath);
static ssize_t proc_status(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_cmdline(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#ifdef CONFIG_SCHED_CPULOAD
static ssize_t proc_loadavg(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif
static ssize_t proc_stack(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_groupstatus(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_groupfd(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);

/* File system methods */

static int     proc_open(FAR struct file *filep, FAR const char *relpath,
                 int oflags, mode_t mode);
static int     proc_close(FAR struct file *filep);
static ssize_t proc_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);

static int     proc_dup(FAR const struct file *oldp,
                 FAR struct file *newp);

static int     proc_opendir(const char *relpath, FAR struct fs_dirent_s *dir);
static int     proc_closedir(FAR struct fs_dirent_s *dir);
static int     proc_readdir(FAR struct fs_dirent_s *dir);
static int     proc_rewinddir(FAR struct fs_dirent_s *dir);

static int     proc_stat(FAR const char *relpath, FAR struct stat *buf);

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/****************************************************************************
 * Public Variables
 ****************************************************************************/

/* 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 procfs_operations proc_operations =
{
  proc_open,          /* open */
  proc_close,         /* close */
  proc_read,          /* read */
  NULL,               /* write */

  proc_dup,           /* dup */

  proc_opendir,       /* opendir */
  proc_closedir,      /* closedir */
  proc_readdir,       /* readdir */
  proc_rewinddir,     /* rewinddir */

  proc_stat           /* stat */
};

/* These structures provide information about every node */

static const struct proc_node_s g_level0node =
{
  "",             "",        (uint8_t)PROC_LEVEL0,       DTYPE_DIRECTORY   /* Top-level directory */
};

static const struct proc_node_s g_status =
{
  "status",       "status",  (uint8_t)PROC_STATUS,       DTYPE_FILE        /* Task/thread status */
};

static const struct proc_node_s g_cmdline =
{
  "cmdline",      "cmdline", (uint8_t)PROC_CMDLINE,      DTYPE_FILE        /* Task command line */
};

#ifdef CONFIG_SCHED_CPULOAD
static const struct proc_node_s g_loadavg =
{
  "loadavg",       "loadavg", (uint8_t)PROC_LOADAVG,     DTYPE_FILE        /* Average CPU utilization */
};
#endif

static const struct proc_node_s g_stack =
{
  "stack",        "stack",   (uint8_t)PROC_STACK,        DTYPE_FILE        /* Task stack info */
};

static const struct proc_node_s g_group =
{
  "group",        "group",   (uint8_t)PROC_GROUP,        DTYPE_DIRECTORY   /* Group directory */
};

static const struct proc_node_s g_groupstatus =
{
  "group/status", "status",  (uint8_t)PROC_GROUP_STATUS, DTYPE_FILE        /* Task group status */
};

static const struct proc_node_s g_groupfd =
{
  "group/fd",     "fd",      (uint8_t)PROC_GROUP_FD,     DTYPE_FILE        /* Group file descriptors */
};

/* This is the list of all nodes */

static FAR const struct proc_node_s * const g_nodeinfo[] =
{
  &g_status,       /* Task/thread status */
  &g_cmdline,      /* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
  &g_loadavg,      /* Average CPU utilization */
#endif
  &g_stack,        /* Task stack info */
  &g_group,        /* Group directory */
  &g_groupstatus,  /* Task group status */
  &g_groupfd       /* Group file descriptors */
};
#define PROC_NNODES (sizeof(g_nodeinfo)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all level0 nodes */

static const struct proc_node_s * const g_level0info[] =
{
  &g_status,       /* Task/thread status */
  &g_cmdline,      /* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
  &g_loadavg,      /* Average CPU utilization */
#endif
  &g_stack,        /* Task stack info */
  &g_group,        /* Group directory */
};
#define PROC_NLEVEL0NODES (sizeof(g_level0info)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all group sub-directory nodes */

static const struct proc_node_s * const g_groupinfo[] =
{
  &g_groupstatus,  /* Task group status */
  &g_groupfd       /* Group file descriptors */
};
#define PROC_NGROUPNODES (sizeof(g_groupinfo)/sizeof(FAR const struct proc_node_s * const))

/* Names of task/thread states */

static const char *g_statenames[] =
{
  "Invalid",
  "Pending unlock",
  "Ready",
  "Running",
  "Inactive",
  "Semaphore wait",
#ifndef CONFIG_DISABLE_MQUEUE
  "Signal wait",
#endif
#ifndef CONFIG_DISABLE_MQUEUE
  "MQ not empty wait",
  "MQ no full wait"
#endif
};

static const char *g_ttypenames[4] =
{
  "Task",
  "pthread",
  "Kernel thread",
  "--?--"
};

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

/****************************************************************************
 * Name: proc_findnode
 ****************************************************************************/

static FAR const struct proc_node_s *proc_findnode(FAR const char *relpath)
{
  int i;

  /* Search every string in g_nodeinfo or until a match is found */

  for (i = 0; i < PROC_NNODES; i++)
    {
      if (strcmp(g_nodeinfo[i]->relpath, relpath) == 0)
        {
          return g_nodeinfo[i];
        }
    }

  /* Not found */

  return NULL;
}

/****************************************************************************
 * Name: proc_status
 ****************************************************************************/

static ssize_t proc_status(FAR struct proc_file_s *procfile,
                           FAR struct tcb_s *tcb, FAR char *buffer,
                           size_t buflen, off_t offset)
{
  FAR const char *policy;
  FAR const char *name;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the task name */

#if CONFIG_TASK_NAME_SIZE > 0
  name       = tcb->name;
#else
  name       = "<noname>";
#endif
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n",
                        "Name:", name);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread type */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "Type:",
                        g_ttypenames[(tcb->flags & TCB_FLAG_TTYPE_MASK) >>
                        TCB_FLAG_TTYPE_SHIFT]);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread state */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "State:",
                        g_statenames[tcb->task_state]);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread priority */

#ifdef CONFIG_PRIORITY_INHERITANCE
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d (%d)\n", "Priority:",
                        tcb->sched_priority, tcb->base_priority);
#else
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "Priority:",
                        tcb->sched_priority);
#endif
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the scheduler policy */

  policy     = g_policy[(tcb->flags & TCB_FLAG_POLICY_MASK) >> TCB_FLAG_POLICY_SHIFT];
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "Scheduler:",
                        policy);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the signal mask */

#ifndef CONFIG_DISABLE_SIGNALS
  linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%08x\n", "SigMask:",
                      tcb->sigprocmask);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_cmdline
 ****************************************************************************/

static ssize_t proc_cmdline(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  FAR struct task_tcb_s *ttcb;
  FAR const char *name;
  FAR char **argv;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the task name */

#if CONFIG_TASK_NAME_SIZE > 0
  name       = tcb->name;
#else
  name       = "<noname>";
#endif
  linesize   = strlen(name);
  memcpy(procfile->line, name, linesize);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

#ifndef CONFIG_DISABLE_PTHREAD
  /* Show the pthread argument */

  if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_PTHREAD)
    {
      FAR struct pthread_tcb_s *ptcb = (FAR struct pthread_tcb_s *)tcb;

      linesize   = snprintf(procfile->line, STATUS_LINELEN, " 0x%p\n", ptcb->arg);
      copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      return totalsize;
    }
#endif

  /* Show the task argument list (skipping over the name) */

  ttcb = (FAR struct task_tcb_s *)tcb;

  for (argv = ttcb->argv + 1; *argv; argv++)
    {
      linesize   = snprintf(procfile->line, STATUS_LINELEN, " %s", *argv);
      copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      if (totalsize >= buflen)
        {
          return totalsize;
        }
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  return totalsize;
}

/****************************************************************************
 * Name: proc_loadavg
 ****************************************************************************/

#ifdef CONFIG_SCHED_CPULOAD
static ssize_t proc_loadavg(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  struct cpuload_s cpuload;
  uint32_t intpart;
  uint32_t fracpart;
  size_t linesize;
  size_t copysize;

  /* Sample the counts for the thread.  clock_cpuload should only fail if
   * the PID is not valid.  This could happen if the thread exited sometime
   * after the procfs entry was opened.
   */

  (void)clock_cpuload(procfile->pid, &cpuload);

  /* On the simulator, you may hit cpuload.total == 0, but probably never on
   * real hardware.
   */

  if (cpuload.total > 0)
    {
      uint32_t tmp;

      tmp      = (1000 * cpuload.active) / cpuload.total;
      intpart  = tmp / 10;
      fracpart = tmp - 10 * intpart;
    }
  else
    {
      intpart  = 0;
      fracpart = 0;
    }

  linesize = snprintf(procfile->line, STATUS_LINELEN, "%3d.%01d%%",
                      intpart, fracpart);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen, &offset);

  return copysize;
}
#endif

/****************************************************************************
 * Name: proc_stack
 ****************************************************************************/

static ssize_t proc_stack(FAR struct proc_file_s *procfile,
                          FAR struct tcb_s *tcb, FAR char *buffer,
                          size_t buflen, off_t offset)
{
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the stack base address */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s0x%p\n",
                        "StackBase:", tcb->adj_stack_ptr);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack size */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%ld\n",
                        "StackSize:", (long)tcb->adj_stack_size);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

#ifdef CONFIG_STACK_COLORATION
  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack size */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%ld\n",
                        "StackUsed:", (long)up_check_tcbstack(tcb));
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupstatus
 ****************************************************************************/

static ssize_t proc_groupstatus(FAR struct proc_file_s *procfile,
                                FAR struct tcb_s *tcb, FAR char *buffer,
                                size_t buflen, off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
#ifdef HAVE_GROUP_MEMBERS
  int i;
#endif

  DEBUGASSERT(group);

  remaining = buflen;
  totalsize = 0;

  /* Show the group IDs */

#ifdef HAVE_GROUP_MEMBERS
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Group ID:", group->tg_gid);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Parent ID:", group->tg_pgid);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

#if !defined(CONFIG_DISABLE_PTHREAD) && defined(CONFIG_SCHED_HAVE_PARENT)
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Main task:", group->tg_task);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s0x%02x\n",
                        "Flags:", group->tg_flags);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Members:", group->tg_nmembers);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

#ifdef HAVE_GROUP_MEMBERS
  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "Member IDs:");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  for (i = 0; i < group->tg_nmembers; i++)
    {
      linesize   = snprintf(procfile->line, STATUS_LINELEN, " %d");
      copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      if (totalsize >= buflen)
        {
          return totalsize;
        }
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupfd
 ****************************************************************************/

static ssize_t proc_groupfd(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
#if CONFIG_NFILE_DESCRIPTORS > 0 /* Guaranteed to be true */
  FAR struct file *file;
#endif
#if CONFIG_NSOCKET_DESCRIPTORS > 0
  FAR struct socket *socket;
#endif
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
  int i;

  DEBUGASSERT(group);

  remaining = buflen;
  totalsize = 0;

#if CONFIG_NFILE_DESCRIPTORS > 0 /* Guaranteed to be true */
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n%3-s %-8s %s\n",
                        "FD", "POS", "OFLAGS");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Examine each open file descriptor */

  for (i = 0, file = group->tg_filelist.fl_files; i < CONFIG_NFILE_DESCRIPTORS; i++, file++)
    {
      /* Is there an inode associated with the file descriptor? */

      if (file->f_inode)
        {
          linesize   = snprintf(procfile->line, STATUS_LINELEN, "%3d %8ld %04x\n",
                                i, (long)file->f_pos, file->f_oflags);
          copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

          totalsize += copysize;
          buffer    += copysize;
          remaining -= copysize;

          if (totalsize >= buflen)
            {
              return totalsize;
            }
        }
    }
#endif

#if CONFIG_NSOCKET_DESCRIPTORS > 0
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n%3-s %-2s %-3s %s\n",
                        "SD", "RF", "TYP", "FLAGS");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Examine each open socket descriptor */

  for (i = 0, socket = group->tg_socketlist.sl_sockets; i < CONFIG_NSOCKET_DESCRIPTORS; i++, socket++)
    {
      /* Is there an connection associated with the socket descriptor? */

      if (socket->s_conn)
        {
          linesize   = snprintf(procfile->line, STATUS_LINELEN, "%3d %2d %3d %02x",
                                i + CONFIG_NFILE_DESCRIPTORS,
                                (long)socket->s_crefs, socket->s_type, socket->s_flags);
          copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

          totalsize += copysize;
          buffer    += copysize;
          remaining -= copysize;

          if (totalsize >= buflen)
            {
              return totalsize;
            }
        }
    }
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_open
 ****************************************************************************/

static int proc_open(FAR struct file *filep, FAR const char *relpath,
                     int oflags, mode_t mode)
{
  FAR struct proc_file_s *procfile;
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  FAR char *ptr;
  irqstate_t flags;
  unsigned long tmp;
  pid_t pid;

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

  /* PROCFS is read-only.  Any attempt to open with any kind of write
   * access is not permitted.
   *
   * REVISIT:  Write-able proc files could be quite useful.
   */

  if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0)
    {
      fdbg("ERROR: Only O_RDONLY supported\n");
      return -EACCES;
    }

  /* The first segment of the relative path should be a task/thread ID */

  ptr = NULL;
  tmp = strtoul(relpath, &ptr, 10);

  if (!ptr || *ptr != '/')
    {
      fdbg("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* Skip over the slash */

  ptr++;

  /* A valid PID would be in the range of 0-32767 (0 is reserved for the
   * IDLE thread).
   */

  if (tmp >= 32768)
    {
      fdbg("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  flags = irqsave();
  tcb = sched_gettcb(pid);
  irqrestore(flags);

  if (!tcb)
    {
      fdbg("ERROR: PID %d is no longer valid\n", (int)pid);
      return -ENOENT;
    }

  /* The remaining segments of the relpath should be a well known node in
   * the task/thread tree.
   */

  node = proc_findnode(ptr);
  if (!node)
    {
      fdbg("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* The node must be a file, not a directory */

  if (node->dtype != DTYPE_FILE)
    {
      fdbg("ERROR: Path \"%s\" is a directory\n", relpath);
      return -EISDIR;
    }

  /* Allocate a container to hold the task and node selection */

  procfile = (FAR struct proc_file_s *)kmm_zalloc(sizeof(struct proc_file_s));
  if (!procfile)
    {
      fdbg("ERROR: Failed to allocate file container\n");
      return -ENOMEM;
    }

  /* Initialize the file container */

  procfile->pid  = pid;
  procfile->node = node;

  /* Save the index as the open-specific state in filep->f_priv */

  filep->f_priv = (FAR void *)procfile;
  return OK;
}

/****************************************************************************
 * Name: proc_close
 ****************************************************************************/

static int proc_close(FAR struct file *filep)
{
  FAR struct proc_file_s *procfile;

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

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile);

  /* Release the file container structure */

  kmm_free(procfile);
  filep->f_priv = NULL;
  return OK;
}

/****************************************************************************
 * Name: proc_read
 ****************************************************************************/

static ssize_t proc_read(FAR struct file *filep, FAR char *buffer,
                         size_t buflen)
{
  FAR struct proc_file_s *procfile;
  FAR struct tcb_s *tcb;
  irqstate_t flags;
  ssize_t ret;

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

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

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile);

  /* Verify that the thread is still valid */

  flags = irqsave();
  tcb = sched_gettcb(procfile->pid);

  if (!tcb)
    {
      fdbg("ERROR: PID %d is not valid\n", (int)procfile->pid);
      irqrestore(flags);
      return -ENODEV;
    }

  /* Provide the requested data */

  switch (procfile->node->node)
    {
    case PROC_STATUS: /* Task/thread status */
      ret = proc_status(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_CMDLINE: /* Task command line */
      ret = proc_cmdline(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

#ifdef CONFIG_SCHED_CPULOAD
    case PROC_LOADAVG: /* Average CPU utilization */
      ret = proc_loadavg(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
    case PROC_STACK: /* Task stack info */
      ret = proc_stack(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_GROUP_STATUS: /* Task group status */
      ret = proc_groupstatus(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_GROUP_FD: /* Group file descriptors */
      ret = proc_groupfd(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

     default:
      ret = -EINVAL;
      break;
    }

  irqrestore(flags);

  /* Update the file offset */

  if (ret > 0)
    {
      filep->f_pos += ret;
    }

  return ret;
}

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

static int proc_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct proc_file_s *oldfile;
  FAR struct proc_file_s *newfile;

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

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

  oldfile = (FAR struct proc_file_s *)oldp->f_priv;
  DEBUGASSERT(oldfile);

  /* Allocate a new container to hold the task and node selection */

  newfile = (FAR struct proc_file_s *)kmm_malloc(sizeof(struct proc_file_s));
  if (!newfile)
    {
      fdbg("ERROR: Failed to allocate file container\n");
      return -ENOMEM;
    }

  /* The copy the file information from the old container to the new */

  memcpy(newfile, oldfile, sizeof(struct proc_file_s));

  /* Save the new container in the new file structure */

  newp->f_priv = (FAR void *)newfile;
  return OK;
}

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

static int proc_opendir(FAR const char *relpath, FAR struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *procdir;
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  irqstate_t flags;
  unsigned long tmp;
  FAR char *ptr;
  pid_t pid;

  fvdbg("relpath: \"%s\"\n", relpath ? relpath : "NULL");
  DEBUGASSERT(relpath && dir && !dir->u.procfs);

  /* The relative must be either:
   *
   *  (1) "<pid>" - The sub-directory of task/thread attributes, or
   *  (2) The name of a directory node under <pid>
   */

  /* Otherwise, the relative path should be a valid task/thread ID */

  ptr = NULL;
  tmp = strtoul(relpath, &ptr, 10);

  if (!ptr || (*ptr != '\0' && *ptr != '/'))
    {
      /* strtoul failed or there is something in the path after the pid */

      fdbg("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
   }

  /* A valid PID would be in the range of 0-32767 (0 is reserved for the
   * IDLE thread).
   */

  if (tmp >= 32768)
    {
      fdbg("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  flags = irqsave();
  tcb = sched_gettcb(pid);
  irqrestore(flags);

  if (!tcb)
    {
      fdbg("ERROR: PID %d is not valid\n", (int)pid);
      return -ENOENT;
    }

  /* Allocate the directory structure.  Note that the index and procentry
   * pointer are implicitly nullified by kmm_zalloc().  Only the remaining,
   * non-zero entries will need be initialized.
   */

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

  /* Was the <pid> the final element of the path? */

  if (*ptr != '\0' && strcmp(ptr, "/") != 0)
    {
      /* There is something in the path after the pid.  Skip over the path
       * segment delimiter and see if we can identify the node of interest.
       */

      ptr++;
      node = proc_findnode(ptr);
      if (!node)
        {
          fdbg("ERROR: Invalid path \"%s\"\n", relpath);
          kmm_free(procdir);
          return -ENOENT;
        }

      /* The node must be a directory, not a file */

      if (node->dtype != DTYPE_DIRECTORY)
        {
          fdbg("ERROR: Path \"%s\" is not a directory\n", relpath);
          kmm_free(procdir);
          return -ENOTDIR;
        }

      /* This is a second level directory */

      procdir->base.level    = 2;
      procdir->base.nentries = PROC_NGROUPNODES;
      procdir->node          = node;
    }
  else
    {
      /* Use the special level0 node */

      procdir->base.level    = 1;
      procdir->base.nentries = PROC_NLEVEL0NODES;
      procdir->node          = &g_level0node;
    }

   procdir->pid  = pid;
   dir->u.procfs = (FAR void *)procdir;
   return OK;
}

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

static int proc_closedir(FAR struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *priv;

  DEBUGASSERT(dir && dir->u.procfs);
  priv = dir->u.procfs;

  if (priv)
    {
      kmm_free(priv);
    }

  dir->u.procfs = NULL;
  return OK;
}

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

static int proc_readdir(struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *procdir;
  FAR const struct proc_node_s *node = NULL;
  FAR struct tcb_s *tcb;
  unsigned int index;
  irqstate_t flags;
  pid_t pid;
  int ret;

  DEBUGASSERT(dir && dir->u.procfs);
  procdir = dir->u.procfs;

  /* Have we reached the end of the directory */

  index = procdir->base.index;
  if (index >= procdir->base.nentries)
    {
      /* We signal the end of the directory by returning the special
       * error -ENOENT
       */

      fvdbg("Entry %d: End of directory\n", index);
      ret = -ENOENT;
    }

  /* No, we are not at the end of the directory */

  else
    {
      /* Verify that the pid still refers to an active task/thread */

      pid = procdir->pid;

      flags = irqsave();
      tcb = sched_gettcb(pid);
      irqrestore(flags);

      if (!tcb)
        {
          fdbg("ERROR: PID %d is no longer valid\n", (int)pid);
          return -ENOENT;
        }

      /* The TCB is still valid (or at least was when we entered this function) */
      /* Handle the directory listing by the node type */

      switch (procdir->node->node)
        {
         case PROC_LEVEL0: /* Top level directory */
           DEBUGASSERT(procdir->base.level == 1);
           node = g_level0info[index];
           break;

         case PROC_GROUP:  /* Group sub-directory */
           DEBUGASSERT(procdir->base.level == 2);
           node = g_groupinfo[index];
           break;

         default:
           return -ENOENT;
        }

      /* Save the filename and file type */

      dir->fd_dir.d_type = node->dtype;
      strncpy(dir->fd_dir.d_name, node->name, NAME_MAX+1);

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

      procdir->base.index = index + 1;
      ret = OK;
    }

  return ret;
}

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

static int proc_rewinddir(struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *priv;

  DEBUGASSERT(dir && dir->u.procfs);
  priv = dir->u.procfs;

  priv->base.index = 0;
  return OK;
}

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

static int proc_stat(const char *relpath, struct stat *buf)
{
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  unsigned long tmp;
  FAR char *ptr;
  irqstate_t flags;
  pid_t pid;

  /* Two path forms are accepted:
   *
   * "<pid>" - If <pid> refers to a currently active task/thread, then it
   *   is a directory
   * "<pid>/<node>" - If <node> is a recognized node then, then it
   *   is a file or directory.
   */

  ptr = NULL;
  tmp = strtoul(relpath, &ptr, 10);

  if (!ptr)
    {
      fdbg("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
   }

  /* A valid PID would be in the range of 0-32767 (0 is reserved for the
   * IDLE thread).
   */

  if (tmp >= 32768)
    {
      fdbg("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  flags = irqsave();
  tcb = sched_gettcb(pid);
  irqrestore(flags);

  if (!tcb)
    {
      fdbg("ERROR: PID %d is no longer valid\n", (int)pid);
      return -ENOENT;
    }

  /* Was the <pid> the final element of the path? */

  if (*ptr == '\0' || strcmp(ptr, "/") == 0)
    {
      /* Yes ... It's a read-only directory */

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

  /* Verify that the process ID is followed by valid path segment delimiter */

  else if (*ptr != '/')
    {
      /* We are required to return -ENOENT all all invalid paths */

      fdbg("ERROR: Bad delimiter '%c' in relpath '%s'\n", *ptr, relpath);
      return -ENOENT;
    }
  else
    {
      /* Otherwise, the second segment of the relpath should be a well
       * known node of the task/thread directory structure.
       */

      /* Skip over the path segment delimiter */

      ptr++;

      /* Lookup the well-known node associated with the relative path. */

      node = proc_findnode(ptr);
      if (!node)
        {
          fdbg("ERROR: Invalid path \"%s\"\n", relpath);
          return -ENOENT;
        }

      /* If the node exists, it is the name for a read-only file or
       * directory.
       */

      if (node->dtype == DTYPE_FILE)
        {
          buf->st_mode = S_IFREG|S_IROTH|S_IRGRP|S_IRUSR;
        }
      else
        {
          buf->st_mode = S_IFDIR|S_IROTH|S_IRGRP|S_IRUSR;
        }
    }

  /* File/directory size, access block size */

  buf->st_size    = 0;
  buf->st_blksize = 0;
  buf->st_blocks  = 0;
  return OK;
}

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

#endif /* CONFIG_FS_PROC_EXCLUDE_PROCESS */
#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */
