6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
1895 lines
52 KiB
C
1895 lines
52 KiB
C
/****************************************************************************
|
|
* fs/procfs/fs_procfsproc.c
|
|
*
|
|
* Copyright (C) 2013-2019 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>
|
|
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
# include <time.h>
|
|
#endif
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/sched.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/environ.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/procfs.h>
|
|
#include <nuttx/fs/dirent.h>
|
|
|
|
#if defined(CONFIG_SCHED_CPULOAD) || defined(CONFIG_SCHED_CRITMONITOR)
|
|
# include <nuttx/clock.h>
|
|
#endif
|
|
|
|
#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS)
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* See include/nuttx/sched.h: */
|
|
|
|
#undef HAVE_GROUPID
|
|
|
|
#if defined(CONFIG_SCHED_HAVE_PARENT) && defined(CONFIG_SCHED_CHILD_STATUS)
|
|
# define HAVE_GROUPID 1
|
|
#endif
|
|
|
|
#ifdef CONFIG_DISABLE_PTHREAD
|
|
# undef HAVE_GROUPID
|
|
#endif
|
|
|
|
/* 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
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
PROC_CRITMON, /* Critical section monitor */
|
|
#endif
|
|
PROC_STACK, /* Task stack info */
|
|
PROC_GROUP, /* Group directory */
|
|
PROC_GROUP_STATUS, /* Task group status */
|
|
PROC_GROUP_FD /* Group file descriptors */
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
, PROC_GROUP_ENV /* Group environment variables */
|
|
#endif
|
|
};
|
|
|
|
/* 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 */
|
|
};
|
|
|
|
/* This structure used with the env_foreach() callback */
|
|
|
|
struct proc_envinfo_s
|
|
{
|
|
FAR struct proc_file_s *procfile;
|
|
FAR char *buffer;
|
|
FAR off_t offset;
|
|
size_t buflen;
|
|
size_t remaining;
|
|
size_t totalsize;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* 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
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
static ssize_t proc_critmon(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);
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
static int proc_groupenv_callback(FAR void *arg, FAR const char *pair);
|
|
static ssize_t proc_groupenv(FAR struct proc_file_s *procfile,
|
|
FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
|
|
off_t offset);
|
|
#endif
|
|
|
|
/* 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 Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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 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
|
|
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
static const struct proc_node_s g_critmon =
|
|
{
|
|
"critmon", "critmon", (uint8_t)PROC_CRITMON, DTYPE_FILE /* Critical Section Monitor */
|
|
};
|
|
#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 */
|
|
};
|
|
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
static const struct proc_node_s g_groupenv =
|
|
{
|
|
"group/env", "env", (uint8_t)PROC_GROUP_ENV, DTYPE_FILE /* Group environment variables */
|
|
};
|
|
|
|
#endif
|
|
|
|
/* 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
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
&g_critmon, /* Critical section Monitor */
|
|
#endif
|
|
&g_stack, /* Task stack info */
|
|
&g_group, /* Group directory */
|
|
&g_groupstatus, /* Task group status */
|
|
&g_groupfd /* Group file descriptors */
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
, &g_groupenv /* Group environment variables */
|
|
#endif
|
|
};
|
|
|
|
#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
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
&g_critmon, /* Critical section monitor */
|
|
#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 FAR const struct proc_node_s * const g_groupinfo[] =
|
|
{
|
|
&g_groupstatus, /* Task group status */
|
|
&g_groupfd /* Group file descriptors */
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
, &g_groupenv /* Group environment variables */
|
|
#endif
|
|
};
|
|
#define PROC_NGROUPNODES (sizeof(g_groupinfo)/sizeof(FAR const struct proc_node_s * const))
|
|
|
|
/* Names of task/thread states */
|
|
|
|
static FAR const char *g_statenames[] =
|
|
{
|
|
"Invalid",
|
|
"Waiting,Unlock",
|
|
"Ready",
|
|
#ifdef CONFIG_SMP
|
|
"Assigned",
|
|
#endif
|
|
"Running",
|
|
"Inactive",
|
|
"Waiting,Semaphore",
|
|
"Waiting,Signal"
|
|
#ifndef CONFIG_DISABLE_MQUEUE
|
|
, "Waiting,MQ empty"
|
|
, "Waiting,MQ full"
|
|
#endif
|
|
#ifdef CONFIG_SIG_SIGSTOP_ACTION
|
|
, "Stopped"
|
|
#endif
|
|
};
|
|
|
|
static FAR const char *g_ttypenames[4] =
|
|
{
|
|
"Task",
|
|
"pthread",
|
|
"Kthread",
|
|
"Invalid"
|
|
};
|
|
|
|
/****************************************************************************
|
|
* 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
|
|
*
|
|
* Description:
|
|
* Format:
|
|
*
|
|
* 111111111122222222223
|
|
* 123456789012345678901234567890
|
|
* Name: xxxx... Task/thread name (See
|
|
* CONFIG_TASK_NAME_SIZE)
|
|
* Type: xxxxxxx {Task, pthread, Kthread, Invalid}
|
|
* PPID: xxxxx Parent thread ID
|
|
* Group: xxxxx Group ID
|
|
* CPU: xxx CPU (CONFIG_SMP only)
|
|
* State: xxxxxxxx,xxxxxxxxx {Invalid, Waiting, Ready, Running,
|
|
* Inactive},
|
|
* {Unlock, Semaphore, Signal, MQ empty,
|
|
* MQ full}
|
|
* Flags: xxx N,P,X
|
|
* Priority: nnn Decimal, 0-255
|
|
* Scheduler: xxxxxxxxxxxxxx {SCHED_FIFO, SCHED_RR, SCHED_SPORADIC,
|
|
* SCHED_OTHER}
|
|
* Sigmask: nnnnnnnn Hexadecimal, 32-bit
|
|
*
|
|
****************************************************************************/
|
|
|
|
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)
|
|
{
|
|
#ifdef CONFIG_SCHED_HAVE_PARENT
|
|
FAR struct task_group_s *group;
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
#ifdef CONFIG_SCHED_HAVE_PARENT
|
|
group = tcb->group;
|
|
DEBUGASSERT(group != NULL);
|
|
|
|
#ifdef HAVE_GROUPID
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "Group:",
|
|
group->tg_pgrpid);
|
|
#else
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "PPID:",
|
|
group->tg_ppid);
|
|
#endif
|
|
|
|
copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);
|
|
|
|
totalsize += copysize;
|
|
buffer += copysize;
|
|
remaining -= copysize;
|
|
|
|
if (totalsize >= buflen)
|
|
{
|
|
return totalsize;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
if (tcb->task_state >= FIRST_ASSIGNED_STATE &&
|
|
tcb->task_state <= LAST_ASSIGNED_STATE)
|
|
{
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "CPU:",
|
|
tcb->cpu);
|
|
}
|
|
else
|
|
{
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s---\n", "CPU:");
|
|
}
|
|
|
|
copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);
|
|
|
|
totalsize += copysize;
|
|
buffer += copysize;
|
|
remaining -= copysize;
|
|
|
|
if (totalsize >= buflen)
|
|
{
|
|
return totalsize;
|
|
}
|
|
#endif
|
|
|
|
/* 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 task flags */
|
|
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%c%c%c\n", "Flags:",
|
|
tcb->flags & TCB_FLAG_NONCANCELABLE ? 'N' : '-',
|
|
tcb->flags & TCB_FLAG_CANCEL_PENDING ? 'P' : '-',
|
|
tcb->flags & TCB_FLAG_EXIT_PROCESSING ? 'P' : '-');
|
|
|
|
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 */
|
|
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%08x\n", "SigMask:",
|
|
tcb->sigprocmask);
|
|
copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);
|
|
|
|
totalsize += copysize;
|
|
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.
|
|
*/
|
|
|
|
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_critmon
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
static ssize_t proc_critmon(FAR struct proc_file_s *procfile,
|
|
FAR struct tcb_s *tcb, FAR char *buffer,
|
|
size_t buflen, off_t offset)
|
|
{
|
|
struct timespec maxtime;
|
|
size_t remaining;
|
|
size_t linesize;
|
|
size_t copysize;
|
|
size_t totalsize;
|
|
|
|
remaining = buflen;
|
|
totalsize = 0;
|
|
|
|
/* Convert the for maximum time pre-emption disabled */
|
|
|
|
if (tcb->premp_max > 0)
|
|
{
|
|
up_critmon_convert(tcb->premp_max, &maxtime);
|
|
}
|
|
else
|
|
{
|
|
maxtime.tv_sec = 0;
|
|
maxtime.tv_nsec = 0;
|
|
}
|
|
|
|
/* Reset the maximum */
|
|
|
|
tcb->premp_max = 0;
|
|
|
|
/* Generate output for maximum time pre-emption disabled */
|
|
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%lu.%09lu,",
|
|
(unsigned long)maxtime.tv_sec,
|
|
(unsigned long)maxtime.tv_nsec);
|
|
copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen, &offset);
|
|
|
|
totalsize += copysize;
|
|
buffer += copysize;
|
|
remaining -= copysize;
|
|
|
|
if (totalsize >= buflen)
|
|
{
|
|
return totalsize;
|
|
}
|
|
|
|
/* Convert and generate output for maximum time in a critical section */
|
|
|
|
if (tcb->crit_max > 0)
|
|
{
|
|
up_critmon_convert(tcb->crit_max, &maxtime);
|
|
}
|
|
else
|
|
{
|
|
maxtime.tv_sec = 0;
|
|
maxtime.tv_nsec = 0;
|
|
}
|
|
|
|
/* Reset the maximum */
|
|
|
|
tcb->crit_max = 0;
|
|
|
|
/* Generate output for maximum time in a critical section */
|
|
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "%lu.%09lu\n",
|
|
(unsigned long)maxtime.tv_sec,
|
|
(unsigned long)maxtime.tv_nsec);
|
|
copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen, &offset);
|
|
|
|
totalsize += copysize;
|
|
return totalsize;
|
|
}
|
|
#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 != NULL);
|
|
|
|
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_grpid);
|
|
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_pgrpid);
|
|
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",
|
|
group->tg_members[i]);
|
|
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;
|
|
FAR struct file *file;
|
|
#ifdef CONFIG_NET
|
|
FAR struct socket *socket;
|
|
#endif
|
|
size_t remaining;
|
|
size_t linesize;
|
|
size_t copysize;
|
|
size_t totalsize;
|
|
int i;
|
|
|
|
DEBUGASSERT(group != NULL);
|
|
|
|
remaining = buflen;
|
|
totalsize = 0;
|
|
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%-3s %-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;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_NET
|
|
linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%-3s %-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,
|
|
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_groupenv_callback
|
|
****************************************************************************/
|
|
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
static int proc_groupenv_callback(FAR void *arg, FAR const char *pair)
|
|
{
|
|
FAR struct proc_envinfo_s *info = (FAR struct proc_envinfo_s *)arg;
|
|
FAR const char *src;
|
|
FAR const char *value;
|
|
FAR char *dest;
|
|
char name[16 + 1];
|
|
size_t linesize;
|
|
size_t copysize;
|
|
int namelen;
|
|
|
|
DEBUGASSERT(arg != NULL && pair != NULL);
|
|
|
|
/* Parse the name from the name/value pair */
|
|
|
|
value = NULL;
|
|
namelen = 0;
|
|
|
|
for (src = pair, dest = name; *src != '=' && *src != '\0'; src++)
|
|
{
|
|
if (namelen < 16)
|
|
{
|
|
*dest++ = *src;
|
|
namelen++;
|
|
}
|
|
}
|
|
|
|
/* NUL terminate the name string */
|
|
|
|
*dest = '\0';
|
|
|
|
/* Skip over the '=' to get the value */
|
|
|
|
if (*src == '=')
|
|
{
|
|
value = src + 1;
|
|
}
|
|
else
|
|
{
|
|
value = "";
|
|
}
|
|
|
|
/* Output the header */
|
|
|
|
linesize = snprintf(info->procfile->line, STATUS_LINELEN, "%s=%s\n",
|
|
name, value);
|
|
copysize = procfs_memcpy(info->procfile->line, linesize, info->buffer,
|
|
info->remaining, &info->offset);
|
|
|
|
info->totalsize += copysize;
|
|
info->buffer += copysize;
|
|
info->remaining -= copysize;
|
|
|
|
if (info->totalsize >= info->buflen)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: proc_groupenv
|
|
****************************************************************************/
|
|
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
static ssize_t proc_groupenv(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;
|
|
struct proc_envinfo_s info;
|
|
|
|
DEBUGASSERT(group != NULL);
|
|
|
|
/* Initialize the info structure */
|
|
|
|
info.procfile = procfile;
|
|
info.buffer = buffer;
|
|
info.offset = offset;
|
|
info.buflen = buflen;
|
|
info.remaining = buflen;
|
|
info.totalsize = 0;
|
|
|
|
/* Generate output for each environment variable */
|
|
|
|
env_foreach(group, proc_groupenv_callback, &info);
|
|
return info.totalsize;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
unsigned long tmp;
|
|
pid_t pid;
|
|
|
|
finfo("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)
|
|
{
|
|
ferr("ERROR: Only O_RDONLY supported\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
/* The first segment of the relative path should be a task/thread ID or
|
|
* the string "self".
|
|
*/
|
|
|
|
ptr = NULL;
|
|
|
|
if (strncmp(relpath, "self", 4) == 0)
|
|
{
|
|
tmp = (unsigned long)getpid(); /* Get the PID of the calling task */
|
|
ptr = (FAR char *)relpath + 4; /* Discard const */
|
|
}
|
|
else
|
|
{
|
|
tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
|
|
}
|
|
|
|
if (ptr == NULL || *ptr != '/')
|
|
{
|
|
ferr("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)
|
|
{
|
|
ferr("ERROR: Invalid PID %ld\n", tmp);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Now verify that a task with this task/thread ID exists */
|
|
|
|
pid = (pid_t)tmp;
|
|
|
|
tcb = sched_gettcb(pid);
|
|
if (tcb == NULL)
|
|
{
|
|
ferr("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 == NULL)
|
|
{
|
|
ferr("ERROR: Invalid path \"%s\"\n", relpath);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* The node must be a file, not a directory */
|
|
|
|
if (!DIRENT_ISFILE(node->dtype))
|
|
{
|
|
ferr("ERROR: Path \"%s\" is not a regular file\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 == NULL)
|
|
{
|
|
ferr("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 != NULL);
|
|
|
|
/* 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;
|
|
ssize_t ret;
|
|
|
|
finfo("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 != NULL);
|
|
|
|
/* Verify that the thread is still valid */
|
|
|
|
tcb = sched_gettcb(procfile->pid);
|
|
if (tcb == NULL)
|
|
{
|
|
ferr("ERROR: PID %d is not valid\n", (int)procfile->pid);
|
|
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
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
case PROC_CRITMON: /* Critical section monitor */
|
|
ret = proc_critmon(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;
|
|
|
|
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
|
|
case PROC_GROUP_ENV: /* Group environment variables */
|
|
ret = proc_groupenv(procfile, tcb, buffer, buflen, filep->f_pos);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
finfo("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 != NULL);
|
|
|
|
/* 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 == NULL)
|
|
{
|
|
ferr("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;
|
|
unsigned long tmp;
|
|
FAR char *ptr;
|
|
pid_t pid;
|
|
|
|
finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
|
|
DEBUGASSERT(relpath != NULL && dir != NULL && dir->u.procfs == NULL);
|
|
|
|
/* The relative must be either:
|
|
*
|
|
* (1) "<pid>" - The sub-directory of task/thread attributes,
|
|
* (2) "self" - Which refers to the PID of the calling task, or
|
|
* (3) The name of a directory node under either of those
|
|
*/
|
|
|
|
/* Otherwise, the relative path should be a valid task/thread ID */
|
|
|
|
ptr = NULL;
|
|
|
|
if (strncmp(relpath, "self", 4) == 0)
|
|
{
|
|
tmp = (unsigned long)getpid(); /* Get the PID of the calling task */
|
|
ptr = (FAR char *)relpath + 4; /* Discard const */
|
|
}
|
|
else
|
|
{
|
|
tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
|
|
}
|
|
|
|
if (ptr == NULL || (*ptr != '\0' && *ptr != '/'))
|
|
{
|
|
/* strtoul failed or there is something in the path after the pid */
|
|
|
|
ferr("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)
|
|
{
|
|
ferr("ERROR: Invalid PID %ld\n", tmp);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Now verify that a task with this task/thread ID exists */
|
|
|
|
pid = (pid_t)tmp;
|
|
|
|
tcb = sched_gettcb(pid);
|
|
if (tcb == NULL)
|
|
{
|
|
ferr("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 == NULL)
|
|
{
|
|
ferr("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 == NULL)
|
|
{
|
|
ferr("ERROR: Invalid path \"%s\"\n", relpath);
|
|
kmm_free(procdir);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* The node must be a directory, not a file */
|
|
|
|
if (!DIRENT_ISDIRECTORY(node->dtype))
|
|
{
|
|
ferr("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 != NULL && dir->u.procfs != NULL);
|
|
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;
|
|
pid_t pid;
|
|
int ret;
|
|
|
|
DEBUGASSERT(dir != NULL && dir->u.procfs != NULL);
|
|
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
|
|
*/
|
|
|
|
finfo("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;
|
|
|
|
tcb = sched_gettcb(pid);
|
|
if (tcb == NULL)
|
|
{
|
|
ferr("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 != NULL && dir->u.procfs != NULL);
|
|
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;
|
|
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;
|
|
|
|
if (strncmp(relpath, "self", 4) == 0)
|
|
{
|
|
tmp = (unsigned long)getpid(); /* Get the PID of the calling task */
|
|
ptr = (FAR char *)relpath + 4; /* Discard const */
|
|
}
|
|
else
|
|
{
|
|
tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
|
|
}
|
|
|
|
if (ptr == NULL)
|
|
{
|
|
ferr("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)
|
|
{
|
|
ferr("ERROR: Invalid PID %ld\n", tmp);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Now verify that a task with this task/thread ID exists */
|
|
|
|
pid = (pid_t)tmp;
|
|
|
|
tcb = sched_gettcb(pid);
|
|
if (tcb == NULL)
|
|
{
|
|
ferr("ERROR: PID %d is no longer valid\n", (int)pid);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Was the <pid> the final element of the path? */
|
|
|
|
memset(buf, 0, sizeof(struct stat));
|
|
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 */
|
|
|
|
ferr("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 == NULL)
|
|
{
|
|
ferr("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;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
#endif /* CONFIG_FS_PROCFS_EXCLUDE_PROCESS */
|
|
#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */
|